@codemirror/autocomplete 6.11.1 → 6.13.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,23 @@
1
+ ## 6.13.0 (2024-02-29)
2
+
3
+ ### New features
4
+
5
+ Completions may now provide 'commit characters' that, when typed, commit the completion before inserting the character.
6
+
7
+ ## 6.12.0 (2024-01-12)
8
+
9
+ ### Bug fixes
10
+
11
+ Make sure snippet completions also set `userEvent` to `input.complete`.
12
+
13
+ Fix a crash when the editor lost focus during an update and autocompletion was active.
14
+
15
+ Fix a crash when using a snippet that has only one field, but multiple instances of that field.
16
+
17
+ ### New features
18
+
19
+ The new `activateOnTypingDelay` option allows control over the debounce time before the completions are queried when the user types.
20
+
1
21
  ## 6.11.1 (2023-11-27)
2
22
 
3
23
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -329,6 +329,7 @@ const completionConfig = state.Facet.define({
329
329
  combine(configs) {
330
330
  return state.combineConfig(configs, {
331
331
  activateOnTyping: true,
332
+ activateOnTypingDelay: 100,
332
333
  selectOnOpen: true,
333
334
  override: null,
334
335
  closeOnBlur: true,
@@ -1033,6 +1034,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1033
1034
  this.debounceUpdate = -1;
1034
1035
  this.running = [];
1035
1036
  this.debounceAccept = -1;
1037
+ this.pendingStart = false;
1036
1038
  this.composing = 0 /* CompositionState.None */;
1037
1039
  for (let active of view.state.field(completionState).active)
1038
1040
  if (active.state == 1 /* State.Pending */)
@@ -1066,8 +1068,11 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1066
1068
  }
1067
1069
  if (this.debounceUpdate > -1)
1068
1070
  clearTimeout(this.debounceUpdate);
1071
+ if (update.transactions.some(tr => tr.effects.some(e => e.is(startCompletionEffect))))
1072
+ this.pendingStart = true;
1073
+ let delay = this.pendingStart ? 50 : update.state.facet(completionConfig).activateOnTypingDelay;
1069
1074
  this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
1070
- ? setTimeout(() => this.startUpdate(), 50) : -1;
1075
+ ? setTimeout(() => this.startUpdate(), delay) : -1;
1071
1076
  if (this.composing != 0 /* CompositionState.None */)
1072
1077
  for (let tr of update.transactions) {
1073
1078
  if (getUserEvent(tr) == "input")
@@ -1078,6 +1083,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1078
1083
  }
1079
1084
  startUpdate() {
1080
1085
  this.debounceUpdate = -1;
1086
+ this.pendingStart = false;
1081
1087
  let { state } = this.view, cState = state.field(completionState);
1082
1088
  for (let active of cState.active) {
1083
1089
  if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
@@ -1157,7 +1163,7 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1157
1163
  if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
1158
1164
  let dialog = state.open && view.getTooltip(this.view, state.open.tooltip);
1159
1165
  if (!dialog || !dialog.dom.contains(event.relatedTarget))
1160
- this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1166
+ setTimeout(() => this.view.dispatch({ effects: closeCompletionEffect.of(null) }), 10);
1161
1167
  }
1162
1168
  },
1163
1169
  compositionstart() {
@@ -1173,6 +1179,21 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1173
1179
  }
1174
1180
  }
1175
1181
  });
1182
+ const windows = typeof navigator == "object" && /Win/.test(navigator.platform);
1183
+ const commitCharacters = state.Prec.highest(view.EditorView.domEventHandlers({
1184
+ keydown(event, view) {
1185
+ let field = view.state.field(completionState, false);
1186
+ if (!field || !field.open || field.open.disabled || field.open.selected < 0 ||
1187
+ event.key.length > 1 || event.ctrlKey && !(windows && event.altKey) || event.metaKey)
1188
+ return false;
1189
+ let option = field.open.options[field.open.selected];
1190
+ let result = field.active.find(a => a.source == option.source);
1191
+ let commitChars = option.completion.commitCharacters || result.result.commitCharacters;
1192
+ if (commitChars && commitChars.indexOf(event.key) > -1)
1193
+ applyCompletion(view, option);
1194
+ return false;
1195
+ }
1196
+ }));
1176
1197
 
1177
1198
  const baseTheme = view.EditorView.baseTheme({
1178
1199
  ".cm-tooltip.cm-tooltip-autocomplete": {
@@ -1465,11 +1486,11 @@ function snippet(template) {
1465
1486
  let spec = {
1466
1487
  changes: { from, to, insert: state.Text.of(text) },
1467
1488
  scrollIntoView: true,
1468
- annotations: completion ? pickedCompletion.of(completion) : undefined
1489
+ annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined
1469
1490
  };
1470
1491
  if (ranges.length)
1471
1492
  spec.selection = fieldSelection(ranges, 0);
1472
- if (ranges.length > 1) {
1493
+ if (ranges.some(r => r.field > 0)) {
1473
1494
  let active = new ActiveSnippet(ranges, 0);
1474
1495
  let effects = spec.effects = [setActive.of(active)];
1475
1496
  if (editor.state.field(snippetState, false) === undefined)
@@ -1894,6 +1915,7 @@ Returns an extension that enables autocompletion.
1894
1915
  */
1895
1916
  function autocompletion(config = {}) {
1896
1917
  return [
1918
+ commitCharacters,
1897
1919
  completionState,
1898
1920
  completionConfig.of(config),
1899
1921
  completionPlugin,
package/dist/index.d.cts CHANGED
@@ -54,6 +54,11 @@ interface Completion {
54
54
  */
55
55
  type?: string;
56
56
  /**
57
+ When this option is selected, and one of these characters is
58
+ typed, insert the completion before typing the character.
59
+ */
60
+ commitCharacters?: readonly string[];
61
+ /**
57
62
  When given, should be a number from -99 to 99 that adjusts how
58
63
  this completion is ranked compared to other completions that
59
64
  match the input as well as this one. A negative number moves it
@@ -255,6 +260,12 @@ interface CompletionResult {
255
260
  completion still applies in the new state.
256
261
  */
257
262
  update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null;
263
+ /**
264
+ Set a default set of [commit
265
+ characters](https://codemirror.net/6/docs/ref/#autocomplete.Completion.commitCharacters) for all
266
+ options in this result.
267
+ */
268
+ commitCharacters?: readonly string[];
258
269
  }
259
270
  /**
260
271
  This annotation is added to transactions that are produced by
@@ -275,6 +286,14 @@ interface CompletionConfig {
275
286
  */
276
287
  activateOnTyping?: boolean;
277
288
  /**
289
+ The amount of time to wait for further typing before querying
290
+ completion sources via
291
+ [`activateOnTyping`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.activateOnTyping).
292
+ Defaults to 100, which should be fine unless your completion
293
+ source is very slow and/or doesn't use `validFor`.
294
+ */
295
+ activateOnTypingDelay?: number;
296
+ /**
278
297
  By default, when completion opens, the first option is selected
279
298
  and can be confirmed with
280
299
  [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion). When this
@@ -571,4 +590,4 @@ the currently selected completion.
571
590
  */
572
591
  declare function setSelectedCompletion(index: number): StateEffect<unknown>;
573
592
 
574
- export { CloseBracketConfig, Completion, CompletionContext, CompletionInfo, CompletionResult, CompletionSection, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
593
+ export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
package/dist/index.d.ts CHANGED
@@ -54,6 +54,11 @@ interface Completion {
54
54
  */
55
55
  type?: string;
56
56
  /**
57
+ When this option is selected, and one of these characters is
58
+ typed, insert the completion before typing the character.
59
+ */
60
+ commitCharacters?: readonly string[];
61
+ /**
57
62
  When given, should be a number from -99 to 99 that adjusts how
58
63
  this completion is ranked compared to other completions that
59
64
  match the input as well as this one. A negative number moves it
@@ -255,6 +260,12 @@ interface CompletionResult {
255
260
  completion still applies in the new state.
256
261
  */
257
262
  update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null;
263
+ /**
264
+ Set a default set of [commit
265
+ characters](https://codemirror.net/6/docs/ref/#autocomplete.Completion.commitCharacters) for all
266
+ options in this result.
267
+ */
268
+ commitCharacters?: readonly string[];
258
269
  }
259
270
  /**
260
271
  This annotation is added to transactions that are produced by
@@ -275,6 +286,14 @@ interface CompletionConfig {
275
286
  */
276
287
  activateOnTyping?: boolean;
277
288
  /**
289
+ The amount of time to wait for further typing before querying
290
+ completion sources via
291
+ [`activateOnTyping`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.activateOnTyping).
292
+ Defaults to 100, which should be fine unless your completion
293
+ source is very slow and/or doesn't use `validFor`.
294
+ */
295
+ activateOnTypingDelay?: number;
296
+ /**
278
297
  By default, when completion opens, the first option is selected
279
298
  and can be confirmed with
280
299
  [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion). When this
@@ -571,4 +590,4 @@ the currently selected completion.
571
590
  */
572
591
  declare function setSelectedCompletion(index: number): StateEffect<unknown>;
573
592
 
574
- export { CloseBracketConfig, Completion, CompletionContext, CompletionInfo, CompletionResult, CompletionSection, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
593
+ export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
1
+ import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
2
2
  import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
3
3
  import { syntaxTree, indentUnit } from '@codemirror/language';
4
4
 
@@ -327,6 +327,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
327
327
  combine(configs) {
328
328
  return combineConfig(configs, {
329
329
  activateOnTyping: true,
330
+ activateOnTypingDelay: 100,
330
331
  selectOnOpen: true,
331
332
  override: null,
332
333
  closeOnBlur: true,
@@ -1031,6 +1032,7 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1031
1032
  this.debounceUpdate = -1;
1032
1033
  this.running = [];
1033
1034
  this.debounceAccept = -1;
1035
+ this.pendingStart = false;
1034
1036
  this.composing = 0 /* CompositionState.None */;
1035
1037
  for (let active of view.state.field(completionState).active)
1036
1038
  if (active.state == 1 /* State.Pending */)
@@ -1064,8 +1066,11 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1064
1066
  }
1065
1067
  if (this.debounceUpdate > -1)
1066
1068
  clearTimeout(this.debounceUpdate);
1069
+ if (update.transactions.some(tr => tr.effects.some(e => e.is(startCompletionEffect))))
1070
+ this.pendingStart = true;
1071
+ let delay = this.pendingStart ? 50 : update.state.facet(completionConfig).activateOnTypingDelay;
1067
1072
  this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
1068
- ? setTimeout(() => this.startUpdate(), 50) : -1;
1073
+ ? setTimeout(() => this.startUpdate(), delay) : -1;
1069
1074
  if (this.composing != 0 /* CompositionState.None */)
1070
1075
  for (let tr of update.transactions) {
1071
1076
  if (getUserEvent(tr) == "input")
@@ -1076,6 +1081,7 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1076
1081
  }
1077
1082
  startUpdate() {
1078
1083
  this.debounceUpdate = -1;
1084
+ this.pendingStart = false;
1079
1085
  let { state } = this.view, cState = state.field(completionState);
1080
1086
  for (let active of cState.active) {
1081
1087
  if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
@@ -1155,7 +1161,7 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1155
1161
  if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
1156
1162
  let dialog = state.open && getTooltip(this.view, state.open.tooltip);
1157
1163
  if (!dialog || !dialog.dom.contains(event.relatedTarget))
1158
- this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1164
+ setTimeout(() => this.view.dispatch({ effects: closeCompletionEffect.of(null) }), 10);
1159
1165
  }
1160
1166
  },
1161
1167
  compositionstart() {
@@ -1171,6 +1177,21 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1171
1177
  }
1172
1178
  }
1173
1179
  });
1180
+ const windows = typeof navigator == "object" && /*@__PURE__*//Win/.test(navigator.platform);
1181
+ const commitCharacters = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.domEventHandlers({
1182
+ keydown(event, view) {
1183
+ let field = view.state.field(completionState, false);
1184
+ if (!field || !field.open || field.open.disabled || field.open.selected < 0 ||
1185
+ event.key.length > 1 || event.ctrlKey && !(windows && event.altKey) || event.metaKey)
1186
+ return false;
1187
+ let option = field.open.options[field.open.selected];
1188
+ let result = field.active.find(a => a.source == option.source);
1189
+ let commitChars = option.completion.commitCharacters || result.result.commitCharacters;
1190
+ if (commitChars && commitChars.indexOf(event.key) > -1)
1191
+ applyCompletion(view, option);
1192
+ return false;
1193
+ }
1194
+ }));
1174
1195
 
1175
1196
  const baseTheme = /*@__PURE__*/EditorView.baseTheme({
1176
1197
  ".cm-tooltip.cm-tooltip-autocomplete": {
@@ -1463,11 +1484,11 @@ function snippet(template) {
1463
1484
  let spec = {
1464
1485
  changes: { from, to, insert: Text.of(text) },
1465
1486
  scrollIntoView: true,
1466
- annotations: completion ? pickedCompletion.of(completion) : undefined
1487
+ annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined
1467
1488
  };
1468
1489
  if (ranges.length)
1469
1490
  spec.selection = fieldSelection(ranges, 0);
1470
- if (ranges.length > 1) {
1491
+ if (ranges.some(r => r.field > 0)) {
1471
1492
  let active = new ActiveSnippet(ranges, 0);
1472
1493
  let effects = spec.effects = [setActive.of(active)];
1473
1494
  if (editor.state.field(snippetState, false) === undefined)
@@ -1892,6 +1913,7 @@ Returns an extension that enables autocompletion.
1892
1913
  */
1893
1914
  function autocompletion(config = {}) {
1894
1915
  return [
1916
+ commitCharacters,
1895
1917
  completionState,
1896
1918
  completionConfig.of(config),
1897
1919
  completionPlugin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.11.1",
3
+ "version": "6.13.0",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",