@codemirror/autocomplete 6.14.0 → 6.16.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.16.0 (2024-04-12)
2
+
3
+ ### New features
4
+
5
+ The new `activateOnCompletion` option allows autocompletion to be configured to chain completion activation for some types of completions.
6
+
7
+ ## 6.15.0 (2024-03-13)
8
+
9
+ ### New features
10
+
11
+ The new `filterStrict` option can be used to turn off fuzzy matching of completions.
12
+
1
13
  ## 6.14.0 (2024-03-10)
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,11 +324,31 @@ 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) {
330
349
  return state.combineConfig(configs, {
331
350
  activateOnTyping: true,
351
+ activateOnCompletion: () => false,
332
352
  activateOnTypingDelay: 100,
333
353
  selectOnOpen: true,
334
354
  override: null,
@@ -341,6 +361,7 @@ const completionConfig = state.Facet.define({
341
361
  icons: true,
342
362
  addToOptions: [],
343
363
  positionInfo: defaultPositionInfo,
364
+ filterStrict: false,
344
365
  compareCompletions: (a, b) => a.label.localeCompare(b.label),
345
366
  interactionDelay: 75,
346
367
  updateSyncTime: 100
@@ -350,7 +371,8 @@ const completionConfig = state.Facet.define({
350
371
  icons: (a, b) => a && b,
351
372
  tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
352
373
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
353
- addToOptions: (a, b) => a.concat(b)
374
+ addToOptions: (a, b) => a.concat(b),
375
+ filterStrict: (a, b) => a || b,
354
376
  });
355
377
  }
356
378
  });
@@ -710,6 +732,7 @@ function sortOptions(active, state) {
710
732
  sections.push(typeof section == "string" ? { name } : section);
711
733
  }
712
734
  };
735
+ let conf = state.facet(completionConfig);
713
736
  for (let a of active)
714
737
  if (a.hasResult()) {
715
738
  let getMatch = a.result.getMatch;
@@ -719,11 +742,12 @@ function sortOptions(active, state) {
719
742
  }
720
743
  }
721
744
  else {
722
- let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to));
745
+ let pattern = state.sliceDoc(a.from, a.to), match;
746
+ let matcher = conf.filterStrict ? new StrictMatcher(pattern) : new FuzzyMatcher(pattern);
723
747
  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)));
748
+ if (match = matcher.match(option.label)) {
749
+ let matched = !option.displayLabel ? match.matched : getMatch ? getMatch(option, match.matched) : [];
750
+ addOption(new Option(option, a.source, matched, match.score + (option.boost || 0)));
727
751
  }
728
752
  }
729
753
  }
@@ -741,7 +765,7 @@ function sortOptions(active, state) {
741
765
  }
742
766
  }
743
767
  let result = [], prev = null;
744
- let compare = state.facet(completionConfig).compareCompletions;
768
+ let compare = conf.compareCompletions;
745
769
  for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
746
770
  let cur = opt.completion;
747
771
  if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
@@ -859,7 +883,12 @@ function makeAttrs(id, selected) {
859
883
  return result;
860
884
  }
861
885
  const none = [];
862
- function getUserEvent(tr) {
886
+ function getUserEvent(tr, conf) {
887
+ if (tr.isUserEvent("input.complete")) {
888
+ let completion = tr.annotation(pickedCompletion);
889
+ if (completion && conf.activateOnCompletion(completion))
890
+ return "input";
891
+ }
863
892
  return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
864
893
  }
865
894
  class ActiveSource {
@@ -870,7 +899,7 @@ class ActiveSource {
870
899
  }
871
900
  hasResult() { return false; }
872
901
  update(tr, conf) {
873
- let event = getUserEvent(tr), value = this;
902
+ let event = getUserEvent(tr, conf), value = this;
874
903
  if (event)
875
904
  value = value.handleUserEvent(tr, event, conf);
876
905
  else if (tr.docChanged)
@@ -1049,10 +1078,11 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1049
1078
  }
1050
1079
  update(update) {
1051
1080
  let cState = update.state.field(completionState);
1081
+ let conf = update.state.facet(completionConfig);
1052
1082
  if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
1053
1083
  return;
1054
1084
  let doesReset = update.transactions.some(tr => {
1055
- return (tr.selection || tr.docChanged) && !getUserEvent(tr);
1085
+ return (tr.selection || tr.docChanged) && !getUserEvent(tr, conf);
1056
1086
  });
1057
1087
  for (let i = 0; i < this.running.length; i++) {
1058
1088
  let query = this.running[i];
@@ -1077,12 +1107,12 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1077
1107
  clearTimeout(this.debounceUpdate);
1078
1108
  if (update.transactions.some(tr => tr.effects.some(e => e.is(startCompletionEffect))))
1079
1109
  this.pendingStart = true;
1080
- let delay = this.pendingStart ? 50 : update.state.facet(completionConfig).activateOnTypingDelay;
1110
+ let delay = this.pendingStart ? 50 : conf.activateOnTypingDelay;
1081
1111
  this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
1082
1112
  ? setTimeout(() => this.startUpdate(), delay) : -1;
1083
1113
  if (this.composing != 0 /* CompositionState.None */)
1084
1114
  for (let tr of update.transactions) {
1085
- if (getUserEvent(tr) == "input")
1115
+ if (getUserEvent(tr, conf) == "input")
1086
1116
  this.composing = 2 /* CompositionState.Changed */;
1087
1117
  else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
1088
1118
  this.composing = 3 /* CompositionState.ChangedAndMoved */;
package/dist/index.d.cts CHANGED
@@ -294,6 +294,11 @@ interface CompletionConfig {
294
294
  */
295
295
  activateOnTyping?: boolean;
296
296
  /**
297
+ When given, if a completion that matches the predicate is
298
+ picked, reactivate completion again as if it was typed normally.
299
+ */
300
+ activateOnCompletion?: (completion: Completion) => boolean;
301
+ /**
297
302
  The amount of time to wait for further typing before querying
298
303
  completion sources via
299
304
  [`activateOnTyping`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.activateOnTyping).
@@ -390,6 +395,13 @@ interface CompletionConfig {
390
395
  */
391
396
  compareCompletions?: (a: Completion, b: Completion) => number;
392
397
  /**
398
+ When set to true (the default is false), turn off fuzzy matching
399
+ of completions and only show those that start with the text the
400
+ user typed. Only takes effect for results where
401
+ [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) isn't false.
402
+ */
403
+ filterStrict?: boolean;
404
+ /**
393
405
  By default, commands relating to an open completion only take
394
406
  effect 75 milliseconds after the completion opened, so that key
395
407
  presses made before the user is aware of the tooltip don't go to
package/dist/index.d.ts CHANGED
@@ -294,6 +294,11 @@ interface CompletionConfig {
294
294
  */
295
295
  activateOnTyping?: boolean;
296
296
  /**
297
+ When given, if a completion that matches the predicate is
298
+ picked, reactivate completion again as if it was typed normally.
299
+ */
300
+ activateOnCompletion?: (completion: Completion) => boolean;
301
+ /**
297
302
  The amount of time to wait for further typing before querying
298
303
  completion sources via
299
304
  [`activateOnTyping`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.activateOnTyping).
@@ -390,6 +395,13 @@ interface CompletionConfig {
390
395
  */
391
396
  compareCompletions?: (a: Completion, b: Completion) => number;
392
397
  /**
398
+ When set to true (the default is false), turn off fuzzy matching
399
+ of completions and only show those that start with the text the
400
+ user typed. Only takes effect for results where
401
+ [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) isn't false.
402
+ */
403
+ filterStrict?: boolean;
404
+ /**
393
405
  By default, commands relating to an open completion only take
394
406
  effect 75 milliseconds after the completion opened, so that key
395
407
  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,11 +322,31 @@ 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) {
328
347
  return combineConfig(configs, {
329
348
  activateOnTyping: true,
349
+ activateOnCompletion: () => false,
330
350
  activateOnTypingDelay: 100,
331
351
  selectOnOpen: true,
332
352
  override: null,
@@ -339,6 +359,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
339
359
  icons: true,
340
360
  addToOptions: [],
341
361
  positionInfo: defaultPositionInfo,
362
+ filterStrict: false,
342
363
  compareCompletions: (a, b) => a.label.localeCompare(b.label),
343
364
  interactionDelay: 75,
344
365
  updateSyncTime: 100
@@ -348,7 +369,8 @@ const completionConfig = /*@__PURE__*/Facet.define({
348
369
  icons: (a, b) => a && b,
349
370
  tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
350
371
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
351
- addToOptions: (a, b) => a.concat(b)
372
+ addToOptions: (a, b) => a.concat(b),
373
+ filterStrict: (a, b) => a || b,
352
374
  });
353
375
  }
354
376
  });
@@ -708,6 +730,7 @@ function sortOptions(active, state) {
708
730
  sections.push(typeof section == "string" ? { name } : section);
709
731
  }
710
732
  };
733
+ let conf = state.facet(completionConfig);
711
734
  for (let a of active)
712
735
  if (a.hasResult()) {
713
736
  let getMatch = a.result.getMatch;
@@ -717,11 +740,12 @@ function sortOptions(active, state) {
717
740
  }
718
741
  }
719
742
  else {
720
- let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to));
743
+ let pattern = state.sliceDoc(a.from, a.to), match;
744
+ let matcher = conf.filterStrict ? new StrictMatcher(pattern) : new FuzzyMatcher(pattern);
721
745
  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)));
746
+ if (match = matcher.match(option.label)) {
747
+ let matched = !option.displayLabel ? match.matched : getMatch ? getMatch(option, match.matched) : [];
748
+ addOption(new Option(option, a.source, matched, match.score + (option.boost || 0)));
725
749
  }
726
750
  }
727
751
  }
@@ -739,7 +763,7 @@ function sortOptions(active, state) {
739
763
  }
740
764
  }
741
765
  let result = [], prev = null;
742
- let compare = state.facet(completionConfig).compareCompletions;
766
+ let compare = conf.compareCompletions;
743
767
  for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
744
768
  let cur = opt.completion;
745
769
  if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
@@ -857,7 +881,12 @@ function makeAttrs(id, selected) {
857
881
  return result;
858
882
  }
859
883
  const none = [];
860
- function getUserEvent(tr) {
884
+ function getUserEvent(tr, conf) {
885
+ if (tr.isUserEvent("input.complete")) {
886
+ let completion = tr.annotation(pickedCompletion);
887
+ if (completion && conf.activateOnCompletion(completion))
888
+ return "input";
889
+ }
861
890
  return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
862
891
  }
863
892
  class ActiveSource {
@@ -868,7 +897,7 @@ class ActiveSource {
868
897
  }
869
898
  hasResult() { return false; }
870
899
  update(tr, conf) {
871
- let event = getUserEvent(tr), value = this;
900
+ let event = getUserEvent(tr, conf), value = this;
872
901
  if (event)
873
902
  value = value.handleUserEvent(tr, event, conf);
874
903
  else if (tr.docChanged)
@@ -1047,10 +1076,11 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1047
1076
  }
1048
1077
  update(update) {
1049
1078
  let cState = update.state.field(completionState);
1079
+ let conf = update.state.facet(completionConfig);
1050
1080
  if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
1051
1081
  return;
1052
1082
  let doesReset = update.transactions.some(tr => {
1053
- return (tr.selection || tr.docChanged) && !getUserEvent(tr);
1083
+ return (tr.selection || tr.docChanged) && !getUserEvent(tr, conf);
1054
1084
  });
1055
1085
  for (let i = 0; i < this.running.length; i++) {
1056
1086
  let query = this.running[i];
@@ -1075,12 +1105,12 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1075
1105
  clearTimeout(this.debounceUpdate);
1076
1106
  if (update.transactions.some(tr => tr.effects.some(e => e.is(startCompletionEffect))))
1077
1107
  this.pendingStart = true;
1078
- let delay = this.pendingStart ? 50 : update.state.facet(completionConfig).activateOnTypingDelay;
1108
+ let delay = this.pendingStart ? 50 : conf.activateOnTypingDelay;
1079
1109
  this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
1080
1110
  ? setTimeout(() => this.startUpdate(), delay) : -1;
1081
1111
  if (this.composing != 0 /* CompositionState.None */)
1082
1112
  for (let tr of update.transactions) {
1083
- if (getUserEvent(tr) == "input")
1113
+ if (getUserEvent(tr, conf) == "input")
1084
1114
  this.composing = 2 /* CompositionState.Changed */;
1085
1115
  else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
1086
1116
  this.composing = 3 /* CompositionState.ChangedAndMoved */;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.14.0",
3
+ "version": "6.16.0",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",