@codemirror/autocomplete 6.5.1 → 6.6.1

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,19 @@
1
+ ## 6.6.1 (2023-05-03)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug that made the editor use the completion's original position, rather than its current position, when changes happened in the document while a result was active.
6
+
7
+ ## 6.6.0 (2023-04-27)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix a bug in `insertCompletionText` that caused it to replace the wrong range when a result set's `to` fell after the cursor.
12
+
13
+ ### New features
14
+
15
+ Functions returned by `snippet` can now be called without a completion object.
16
+
1
17
  ## 6.5.1 (2023-04-13)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -166,25 +166,17 @@ completion's text in the main selection range, and any other
166
166
  selection range that has the same text in front of it.
167
167
  */
168
168
  function insertCompletionText(state$1, text, from, to) {
169
- let { main } = state$1.selection, len = to - from;
169
+ let { main } = state$1.selection, fromOff = from - main.from, toOff = to - main.from;
170
170
  return Object.assign(Object.assign({}, state$1.changeByRange(range => {
171
- if (range != main && len &&
172
- state$1.sliceDoc(range.from - len, range.from + to - main.from) != state$1.sliceDoc(from, to))
171
+ if (range != main && from != to &&
172
+ state$1.sliceDoc(range.from + fromOff, range.from + toOff) != state$1.sliceDoc(from, to))
173
173
  return { range };
174
174
  return {
175
- changes: { from: range.from - len, to: to == main.from ? range.to : range.from + to - main.from, insert: text },
176
- range: state.EditorSelection.cursor(range.from - len + text.length)
175
+ changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: text },
176
+ range: state.EditorSelection.cursor(range.from + fromOff + text.length)
177
177
  };
178
178
  })), { userEvent: "input.complete" });
179
179
  }
180
- function applyCompletion(view, option) {
181
- const apply = option.completion.apply || option.completion.label;
182
- let result = option.source;
183
- if (typeof apply == "string")
184
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
185
- else
186
- apply(view, option.completion, result.from, result.to);
187
- }
188
180
  const SourceCache = new WeakMap();
189
181
  function asSource(source) {
190
182
  if (!Array.isArray(source))
@@ -388,6 +380,232 @@ function defaultPositionInfo(view$1, list, option, info, space) {
388
380
  };
389
381
  }
390
382
 
383
+ /**
384
+ Returns a command that moves the completion selection forward or
385
+ backward by the given amount.
386
+ */
387
+ function moveCompletionSelection(forward, by = "option") {
388
+ return (view$1) => {
389
+ let cState = view$1.state.field(completionState, false);
390
+ if (!cState || !cState.open || cState.open.disabled ||
391
+ Date.now() - cState.open.timestamp < view$1.state.facet(completionConfig).interactionDelay)
392
+ return false;
393
+ let step = 1, tooltip;
394
+ if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
395
+ step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
396
+ tooltip.dom.querySelector("li").offsetHeight) - 1);
397
+ let { length } = cState.open.options;
398
+ let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
399
+ if (selected < 0)
400
+ selected = by == "page" ? 0 : length - 1;
401
+ else if (selected >= length)
402
+ selected = by == "page" ? length - 1 : 0;
403
+ view$1.dispatch({ effects: setSelectedEffect.of(selected) });
404
+ return true;
405
+ };
406
+ }
407
+ /**
408
+ Accept the current completion.
409
+ */
410
+ const acceptCompletion = (view) => {
411
+ let cState = view.state.field(completionState, false);
412
+ if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
413
+ Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
414
+ return false;
415
+ if (!cState.open.disabled)
416
+ return applyCompletion(view, cState.open.options[cState.open.selected]);
417
+ return true;
418
+ };
419
+ /**
420
+ Explicitly start autocompletion.
421
+ */
422
+ const startCompletion = (view) => {
423
+ let cState = view.state.field(completionState, false);
424
+ if (!cState)
425
+ return false;
426
+ view.dispatch({ effects: startCompletionEffect.of(true) });
427
+ return true;
428
+ };
429
+ /**
430
+ Close the currently active completion.
431
+ */
432
+ const closeCompletion = (view) => {
433
+ let cState = view.state.field(completionState, false);
434
+ if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
435
+ return false;
436
+ view.dispatch({ effects: closeCompletionEffect.of(null) });
437
+ return true;
438
+ };
439
+ class RunningQuery {
440
+ constructor(active, context) {
441
+ this.active = active;
442
+ this.context = context;
443
+ this.time = Date.now();
444
+ this.updates = [];
445
+ // Note that 'undefined' means 'not done yet', whereas 'null' means
446
+ // 'query returned null'.
447
+ this.done = undefined;
448
+ }
449
+ }
450
+ const DebounceTime = 50, MaxUpdateCount = 50, MinAbortTime = 1000;
451
+ const completionPlugin = view.ViewPlugin.fromClass(class {
452
+ constructor(view) {
453
+ this.view = view;
454
+ this.debounceUpdate = -1;
455
+ this.running = [];
456
+ this.debounceAccept = -1;
457
+ this.composing = 0 /* CompositionState.None */;
458
+ for (let active of view.state.field(completionState).active)
459
+ if (active.state == 1 /* State.Pending */)
460
+ this.startQuery(active);
461
+ }
462
+ update(update) {
463
+ let cState = update.state.field(completionState);
464
+ if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
465
+ return;
466
+ let doesReset = update.transactions.some(tr => {
467
+ return (tr.selection || tr.docChanged) && !getUserEvent(tr);
468
+ });
469
+ for (let i = 0; i < this.running.length; i++) {
470
+ let query = this.running[i];
471
+ if (doesReset ||
472
+ query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
473
+ for (let handler of query.context.abortListeners) {
474
+ try {
475
+ handler();
476
+ }
477
+ catch (e) {
478
+ view.logException(this.view.state, e);
479
+ }
480
+ }
481
+ query.context.abortListeners = null;
482
+ this.running.splice(i--, 1);
483
+ }
484
+ else {
485
+ query.updates.push(...update.transactions);
486
+ }
487
+ }
488
+ if (this.debounceUpdate > -1)
489
+ clearTimeout(this.debounceUpdate);
490
+ this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
491
+ ? setTimeout(() => this.startUpdate(), DebounceTime) : -1;
492
+ if (this.composing != 0 /* CompositionState.None */)
493
+ for (let tr of update.transactions) {
494
+ if (getUserEvent(tr) == "input")
495
+ this.composing = 2 /* CompositionState.Changed */;
496
+ else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
497
+ this.composing = 3 /* CompositionState.ChangedAndMoved */;
498
+ }
499
+ }
500
+ startUpdate() {
501
+ this.debounceUpdate = -1;
502
+ let { state } = this.view, cState = state.field(completionState);
503
+ for (let active of cState.active) {
504
+ if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
505
+ this.startQuery(active);
506
+ }
507
+ }
508
+ startQuery(active) {
509
+ let { state } = this.view, pos = cur(state);
510
+ let context = new CompletionContext(state, pos, active.explicitPos == pos);
511
+ let pending = new RunningQuery(active, context);
512
+ this.running.push(pending);
513
+ Promise.resolve(active.source(context)).then(result => {
514
+ if (!pending.context.aborted) {
515
+ pending.done = result || null;
516
+ this.scheduleAccept();
517
+ }
518
+ }, err => {
519
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
520
+ view.logException(this.view.state, err);
521
+ });
522
+ }
523
+ scheduleAccept() {
524
+ if (this.running.every(q => q.done !== undefined))
525
+ this.accept();
526
+ else if (this.debounceAccept < 0)
527
+ this.debounceAccept = setTimeout(() => this.accept(), DebounceTime);
528
+ }
529
+ // For each finished query in this.running, try to create a result
530
+ // or, if appropriate, restart the query.
531
+ accept() {
532
+ var _a;
533
+ if (this.debounceAccept > -1)
534
+ clearTimeout(this.debounceAccept);
535
+ this.debounceAccept = -1;
536
+ let updated = [];
537
+ let conf = this.view.state.facet(completionConfig);
538
+ for (let i = 0; i < this.running.length; i++) {
539
+ let query = this.running[i];
540
+ if (query.done === undefined)
541
+ continue;
542
+ this.running.splice(i--, 1);
543
+ if (query.done) {
544
+ let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state));
545
+ // Replay the transactions that happened since the start of
546
+ // the request and see if that preserves the result
547
+ for (let tr of query.updates)
548
+ active = active.update(tr, conf);
549
+ if (active.hasResult()) {
550
+ updated.push(active);
551
+ continue;
552
+ }
553
+ }
554
+ let current = this.view.state.field(completionState).active.find(a => a.source == query.active.source);
555
+ if (current && current.state == 1 /* State.Pending */) {
556
+ if (query.done == null) {
557
+ // Explicitly failed. Should clear the pending status if it
558
+ // hasn't been re-set in the meantime.
559
+ let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
560
+ for (let tr of query.updates)
561
+ active = active.update(tr, conf);
562
+ if (active.state != 1 /* State.Pending */)
563
+ updated.push(active);
564
+ }
565
+ else {
566
+ // Cleared by subsequent transactions. Restart.
567
+ this.startQuery(current);
568
+ }
569
+ }
570
+ }
571
+ if (updated.length)
572
+ this.view.dispatch({ effects: setActiveEffect.of(updated) });
573
+ }
574
+ }, {
575
+ eventHandlers: {
576
+ blur(event) {
577
+ let state = this.view.state.field(completionState, false);
578
+ if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
579
+ let dialog = state.open && view.getTooltip(this.view, state.open.tooltip);
580
+ if (!dialog || !dialog.dom.contains(event.relatedTarget))
581
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
582
+ }
583
+ },
584
+ compositionstart() {
585
+ this.composing = 1 /* CompositionState.Started */;
586
+ },
587
+ compositionend() {
588
+ if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
589
+ // Safari fires compositionend events synchronously, possibly
590
+ // from inside an update, so dispatch asynchronously to avoid reentrancy
591
+ setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
592
+ }
593
+ this.composing = 0 /* CompositionState.None */;
594
+ }
595
+ }
596
+ });
597
+ function applyCompletion(view, option) {
598
+ const apply = option.completion.apply || option.completion.label;
599
+ let result = view.state.field(completionState).active.find(a => a.source == option.source);
600
+ if (!(result instanceof ActiveResult))
601
+ return false;
602
+ if (typeof apply == "string")
603
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
604
+ else
605
+ apply(view, option.completion, result.from, result.to);
606
+ return true;
607
+ }
608
+
391
609
  function optionContent(config) {
392
610
  let content = config.addToOptions.slice();
393
611
  if (config.icons)
@@ -693,14 +911,14 @@ function sortOptions(active, state) {
693
911
  if (getMatch)
694
912
  for (let n of getMatch(option))
695
913
  match.push(n);
696
- addOption(new Option(option, a, match, match[0]));
914
+ addOption(new Option(option, a.source, match, match[0]));
697
915
  }
698
916
  }
699
917
  else {
700
918
  let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
701
919
  for (let option of a.result.options)
702
920
  if (match = matcher.match(option.label)) {
703
- addOption(new Option(option, a, match, match[0] + (option.boost || 0)));
921
+ addOption(new Option(option, a.source, match, match[0] + (option.boost || 0)));
704
922
  }
705
923
  }
706
924
  }
@@ -926,221 +1144,6 @@ const completionState = state.StateField.define({
926
1144
  ]
927
1145
  });
928
1146
 
929
- /**
930
- Returns a command that moves the completion selection forward or
931
- backward by the given amount.
932
- */
933
- function moveCompletionSelection(forward, by = "option") {
934
- return (view$1) => {
935
- let cState = view$1.state.field(completionState, false);
936
- if (!cState || !cState.open || cState.open.disabled ||
937
- Date.now() - cState.open.timestamp < view$1.state.facet(completionConfig).interactionDelay)
938
- return false;
939
- let step = 1, tooltip;
940
- if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
941
- step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
942
- tooltip.dom.querySelector("li").offsetHeight) - 1);
943
- let { length } = cState.open.options;
944
- let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
945
- if (selected < 0)
946
- selected = by == "page" ? 0 : length - 1;
947
- else if (selected >= length)
948
- selected = by == "page" ? length - 1 : 0;
949
- view$1.dispatch({ effects: setSelectedEffect.of(selected) });
950
- return true;
951
- };
952
- }
953
- /**
954
- Accept the current completion.
955
- */
956
- const acceptCompletion = (view) => {
957
- let cState = view.state.field(completionState, false);
958
- if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
959
- Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
960
- return false;
961
- if (!cState.open.disabled)
962
- applyCompletion(view, cState.open.options[cState.open.selected]);
963
- return true;
964
- };
965
- /**
966
- Explicitly start autocompletion.
967
- */
968
- const startCompletion = (view) => {
969
- let cState = view.state.field(completionState, false);
970
- if (!cState)
971
- return false;
972
- view.dispatch({ effects: startCompletionEffect.of(true) });
973
- return true;
974
- };
975
- /**
976
- Close the currently active completion.
977
- */
978
- const closeCompletion = (view) => {
979
- let cState = view.state.field(completionState, false);
980
- if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
981
- return false;
982
- view.dispatch({ effects: closeCompletionEffect.of(null) });
983
- return true;
984
- };
985
- class RunningQuery {
986
- constructor(active, context) {
987
- this.active = active;
988
- this.context = context;
989
- this.time = Date.now();
990
- this.updates = [];
991
- // Note that 'undefined' means 'not done yet', whereas 'null' means
992
- // 'query returned null'.
993
- this.done = undefined;
994
- }
995
- }
996
- const DebounceTime = 50, MaxUpdateCount = 50, MinAbortTime = 1000;
997
- const completionPlugin = view.ViewPlugin.fromClass(class {
998
- constructor(view) {
999
- this.view = view;
1000
- this.debounceUpdate = -1;
1001
- this.running = [];
1002
- this.debounceAccept = -1;
1003
- this.composing = 0 /* CompositionState.None */;
1004
- for (let active of view.state.field(completionState).active)
1005
- if (active.state == 1 /* State.Pending */)
1006
- this.startQuery(active);
1007
- }
1008
- update(update) {
1009
- let cState = update.state.field(completionState);
1010
- if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
1011
- return;
1012
- let doesReset = update.transactions.some(tr => {
1013
- return (tr.selection || tr.docChanged) && !getUserEvent(tr);
1014
- });
1015
- for (let i = 0; i < this.running.length; i++) {
1016
- let query = this.running[i];
1017
- if (doesReset ||
1018
- query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
1019
- for (let handler of query.context.abortListeners) {
1020
- try {
1021
- handler();
1022
- }
1023
- catch (e) {
1024
- view.logException(this.view.state, e);
1025
- }
1026
- }
1027
- query.context.abortListeners = null;
1028
- this.running.splice(i--, 1);
1029
- }
1030
- else {
1031
- query.updates.push(...update.transactions);
1032
- }
1033
- }
1034
- if (this.debounceUpdate > -1)
1035
- clearTimeout(this.debounceUpdate);
1036
- this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
1037
- ? setTimeout(() => this.startUpdate(), DebounceTime) : -1;
1038
- if (this.composing != 0 /* CompositionState.None */)
1039
- for (let tr of update.transactions) {
1040
- if (getUserEvent(tr) == "input")
1041
- this.composing = 2 /* CompositionState.Changed */;
1042
- else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
1043
- this.composing = 3 /* CompositionState.ChangedAndMoved */;
1044
- }
1045
- }
1046
- startUpdate() {
1047
- this.debounceUpdate = -1;
1048
- let { state } = this.view, cState = state.field(completionState);
1049
- for (let active of cState.active) {
1050
- if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
1051
- this.startQuery(active);
1052
- }
1053
- }
1054
- startQuery(active) {
1055
- let { state } = this.view, pos = cur(state);
1056
- let context = new CompletionContext(state, pos, active.explicitPos == pos);
1057
- let pending = new RunningQuery(active, context);
1058
- this.running.push(pending);
1059
- Promise.resolve(active.source(context)).then(result => {
1060
- if (!pending.context.aborted) {
1061
- pending.done = result || null;
1062
- this.scheduleAccept();
1063
- }
1064
- }, err => {
1065
- this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1066
- view.logException(this.view.state, err);
1067
- });
1068
- }
1069
- scheduleAccept() {
1070
- if (this.running.every(q => q.done !== undefined))
1071
- this.accept();
1072
- else if (this.debounceAccept < 0)
1073
- this.debounceAccept = setTimeout(() => this.accept(), DebounceTime);
1074
- }
1075
- // For each finished query in this.running, try to create a result
1076
- // or, if appropriate, restart the query.
1077
- accept() {
1078
- var _a;
1079
- if (this.debounceAccept > -1)
1080
- clearTimeout(this.debounceAccept);
1081
- this.debounceAccept = -1;
1082
- let updated = [];
1083
- let conf = this.view.state.facet(completionConfig);
1084
- for (let i = 0; i < this.running.length; i++) {
1085
- let query = this.running[i];
1086
- if (query.done === undefined)
1087
- continue;
1088
- this.running.splice(i--, 1);
1089
- if (query.done) {
1090
- let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state));
1091
- // Replay the transactions that happened since the start of
1092
- // the request and see if that preserves the result
1093
- for (let tr of query.updates)
1094
- active = active.update(tr, conf);
1095
- if (active.hasResult()) {
1096
- updated.push(active);
1097
- continue;
1098
- }
1099
- }
1100
- let current = this.view.state.field(completionState).active.find(a => a.source == query.active.source);
1101
- if (current && current.state == 1 /* State.Pending */) {
1102
- if (query.done == null) {
1103
- // Explicitly failed. Should clear the pending status if it
1104
- // hasn't been re-set in the meantime.
1105
- let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
1106
- for (let tr of query.updates)
1107
- active = active.update(tr, conf);
1108
- if (active.state != 1 /* State.Pending */)
1109
- updated.push(active);
1110
- }
1111
- else {
1112
- // Cleared by subsequent transactions. Restart.
1113
- this.startQuery(current);
1114
- }
1115
- }
1116
- }
1117
- if (updated.length)
1118
- this.view.dispatch({ effects: setActiveEffect.of(updated) });
1119
- }
1120
- }, {
1121
- eventHandlers: {
1122
- blur(event) {
1123
- let state = this.view.state.field(completionState, false);
1124
- if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
1125
- let dialog = state.open && view.getTooltip(this.view, state.open.tooltip);
1126
- if (!dialog || !dialog.dom.contains(event.relatedTarget))
1127
- this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1128
- }
1129
- },
1130
- compositionstart() {
1131
- this.composing = 1 /* CompositionState.Started */;
1132
- },
1133
- compositionend() {
1134
- if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
1135
- // Safari fires compositionend events synchronously, possibly
1136
- // from inside an update, so dispatch asynchronously to avoid reentrancy
1137
- setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
1138
- }
1139
- this.composing = 0 /* CompositionState.None */;
1140
- }
1141
- }
1142
- });
1143
-
1144
1147
  const baseTheme = view.EditorView.baseTheme({
1145
1148
  ".cm-tooltip.cm-tooltip-autocomplete": {
1146
1149
  "& > ul": {
@@ -1432,7 +1435,7 @@ function snippet(template) {
1432
1435
  let spec = {
1433
1436
  changes: { from, to, insert: state.Text.of(text) },
1434
1437
  scrollIntoView: true,
1435
- annotations: pickedCompletion.of(completion)
1438
+ annotations: completion ? pickedCompletion.of(completion) : undefined
1436
1439
  };
1437
1440
  if (ranges.length)
1438
1441
  spec.selection = fieldSelection(ranges, 0);
package/dist/index.d.ts CHANGED
@@ -3,109 +3,6 @@ import { EditorState, TransactionSpec, Transaction, StateCommand, Facet, Extensi
3
3
  import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
4
4
  import * as _lezer_common from '@lezer/common';
5
5
 
6
- interface CompletionConfig {
7
- /**
8
- When enabled (defaults to true), autocompletion will start
9
- whenever the user types something that can be completed.
10
- */
11
- activateOnTyping?: boolean;
12
- /**
13
- By default, when completion opens, the first option is selected
14
- and can be confirmed with
15
- [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion). When this
16
- is set to false, the completion widget starts with no completion
17
- selected, and the user has to explicitly move to a completion
18
- before you can confirm one.
19
- */
20
- selectOnOpen?: boolean;
21
- /**
22
- Override the completion sources used. By default, they will be
23
- taken from the `"autocomplete"` [language
24
- data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) (which should hold
25
- [completion sources](https://codemirror.net/6/docs/ref/#autocomplete.CompletionSource) or arrays
26
- of [completions](https://codemirror.net/6/docs/ref/#autocomplete.Completion)).
27
- */
28
- override?: readonly CompletionSource[] | null;
29
- /**
30
- Determines whether the completion tooltip is closed when the
31
- editor loses focus. Defaults to true.
32
- */
33
- closeOnBlur?: boolean;
34
- /**
35
- The maximum number of options to render to the DOM.
36
- */
37
- maxRenderedOptions?: number;
38
- /**
39
- Set this to false to disable the [default completion
40
- keymap](https://codemirror.net/6/docs/ref/#autocomplete.completionKeymap). (This requires you to
41
- add bindings to control completion yourself. The bindings should
42
- probably have a higher precedence than other bindings for the
43
- same keys.)
44
- */
45
- defaultKeymap?: boolean;
46
- /**
47
- By default, completions are shown below the cursor when there is
48
- space. Setting this to true will make the extension put the
49
- completions above the cursor when possible.
50
- */
51
- aboveCursor?: boolean;
52
- /**
53
- When given, this may return an additional CSS class to add to
54
- the completion dialog element.
55
- */
56
- tooltipClass?: (state: EditorState) => string;
57
- /**
58
- This can be used to add additional CSS classes to completion
59
- options.
60
- */
61
- optionClass?: (completion: Completion) => string;
62
- /**
63
- By default, the library will render icons based on the
64
- completion's [type](https://codemirror.net/6/docs/ref/#autocomplete.Completion.type) in front of
65
- each option. Set this to false to turn that off.
66
- */
67
- icons?: boolean;
68
- /**
69
- This option can be used to inject additional content into
70
- options. The `render` function will be called for each visible
71
- completion, and should produce a DOM node to show. `position`
72
- determines where in the DOM the result appears, relative to
73
- other added widgets and the standard content. The default icons
74
- have position 20, the label position 50, and the detail position
75
- 80.
76
- */
77
- addToOptions?: {
78
- render: (completion: Completion, state: EditorState) => Node | null;
79
- position: number;
80
- }[];
81
- /**
82
- By default, [info](https://codemirror.net/6/docs/ref/#autocomplet.Completion.info) tooltips are
83
- placed to the side of the selected. This option can be used to
84
- override that. It will be given rectangles for the list of
85
- completions, the selected option, the info element, and the
86
- availble [tooltip space](https://codemirror.net/6/docs/ref/#view.tooltips^config.tooltipSpace),
87
- and should return style and/or class strings for the info
88
- element.
89
- */
90
- positionInfo?: (view: EditorView, list: Rect, option: Rect, info: Rect, space: Rect) => {
91
- style?: string;
92
- class?: string;
93
- };
94
- /**
95
- The comparison function to use when sorting completions with the same
96
- match score. Defaults to using
97
- [`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
98
- */
99
- compareCompletions?: (a: Completion, b: Completion) => number;
100
- /**
101
- By default, commands relating to an open completion only take
102
- effect 75 milliseconds after the completion opened, so that key
103
- presses made before the user is aware of the tooltip don't go to
104
- the tooltip. This option can be used to configure that delay.
105
- */
106
- interactionDelay?: number;
107
- }
108
-
109
6
  /**
110
7
  Objects type used to represent individual completions.
111
8
  */
@@ -351,6 +248,109 @@ selection range that has the same text in front of it.
351
248
  */
352
249
  declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
353
250
 
251
+ interface CompletionConfig {
252
+ /**
253
+ When enabled (defaults to true), autocompletion will start
254
+ whenever the user types something that can be completed.
255
+ */
256
+ activateOnTyping?: boolean;
257
+ /**
258
+ By default, when completion opens, the first option is selected
259
+ and can be confirmed with
260
+ [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion). When this
261
+ is set to false, the completion widget starts with no completion
262
+ selected, and the user has to explicitly move to a completion
263
+ before you can confirm one.
264
+ */
265
+ selectOnOpen?: boolean;
266
+ /**
267
+ Override the completion sources used. By default, they will be
268
+ taken from the `"autocomplete"` [language
269
+ data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) (which should hold
270
+ [completion sources](https://codemirror.net/6/docs/ref/#autocomplete.CompletionSource) or arrays
271
+ of [completions](https://codemirror.net/6/docs/ref/#autocomplete.Completion)).
272
+ */
273
+ override?: readonly CompletionSource[] | null;
274
+ /**
275
+ Determines whether the completion tooltip is closed when the
276
+ editor loses focus. Defaults to true.
277
+ */
278
+ closeOnBlur?: boolean;
279
+ /**
280
+ The maximum number of options to render to the DOM.
281
+ */
282
+ maxRenderedOptions?: number;
283
+ /**
284
+ Set this to false to disable the [default completion
285
+ keymap](https://codemirror.net/6/docs/ref/#autocomplete.completionKeymap). (This requires you to
286
+ add bindings to control completion yourself. The bindings should
287
+ probably have a higher precedence than other bindings for the
288
+ same keys.)
289
+ */
290
+ defaultKeymap?: boolean;
291
+ /**
292
+ By default, completions are shown below the cursor when there is
293
+ space. Setting this to true will make the extension put the
294
+ completions above the cursor when possible.
295
+ */
296
+ aboveCursor?: boolean;
297
+ /**
298
+ When given, this may return an additional CSS class to add to
299
+ the completion dialog element.
300
+ */
301
+ tooltipClass?: (state: EditorState) => string;
302
+ /**
303
+ This can be used to add additional CSS classes to completion
304
+ options.
305
+ */
306
+ optionClass?: (completion: Completion) => string;
307
+ /**
308
+ By default, the library will render icons based on the
309
+ completion's [type](https://codemirror.net/6/docs/ref/#autocomplete.Completion.type) in front of
310
+ each option. Set this to false to turn that off.
311
+ */
312
+ icons?: boolean;
313
+ /**
314
+ This option can be used to inject additional content into
315
+ options. The `render` function will be called for each visible
316
+ completion, and should produce a DOM node to show. `position`
317
+ determines where in the DOM the result appears, relative to
318
+ other added widgets and the standard content. The default icons
319
+ have position 20, the label position 50, and the detail position
320
+ 80.
321
+ */
322
+ addToOptions?: {
323
+ render: (completion: Completion, state: EditorState) => Node | null;
324
+ position: number;
325
+ }[];
326
+ /**
327
+ By default, [info](https://codemirror.net/6/docs/ref/#autocomplet.Completion.info) tooltips are
328
+ placed to the side of the selected. This option can be used to
329
+ override that. It will be given rectangles for the list of
330
+ completions, the selected option, the info element, and the
331
+ availble [tooltip space](https://codemirror.net/6/docs/ref/#view.tooltips^config.tooltipSpace),
332
+ and should return style and/or class strings for the info
333
+ element.
334
+ */
335
+ positionInfo?: (view: EditorView, list: Rect, option: Rect, info: Rect, space: Rect) => {
336
+ style?: string;
337
+ class?: string;
338
+ };
339
+ /**
340
+ The comparison function to use when sorting completions with the same
341
+ match score. Defaults to using
342
+ [`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
343
+ */
344
+ compareCompletions?: (a: Completion, b: Completion) => number;
345
+ /**
346
+ By default, commands relating to an open completion only take
347
+ effect 75 milliseconds after the completion opened, so that key
348
+ presses made before the user is aware of the tooltip don't go to
349
+ the tooltip. This option can be used to configure that delay.
350
+ */
351
+ interactionDelay?: number;
352
+ }
353
+
354
354
  /**
355
355
  Convert a snippet template to a function that can
356
356
  [apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
@@ -384,7 +384,7 @@ interpreted as indicating a placeholder.
384
384
  declare function snippet(template: string): (editor: {
385
385
  state: EditorState;
386
386
  dispatch: (tr: Transaction) => void;
387
- }, completion: Completion, from: number, to: number) => void;
387
+ }, completion: Completion | null, from: number, to: number) => void;
388
388
  /**
389
389
  A command that clears the active snippet, if any.
390
390
  */
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
2
- import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
2
+ import { Direction, ViewPlugin, logException, getTooltip, showTooltip, EditorView, Decoration, WidgetType, keymap } from '@codemirror/view';
3
3
  import { syntaxTree, indentUnit } from '@codemirror/language';
4
4
 
5
5
  /**
@@ -162,25 +162,17 @@ completion's text in the main selection range, and any other
162
162
  selection range that has the same text in front of it.
163
163
  */
164
164
  function insertCompletionText(state, text, from, to) {
165
- let { main } = state.selection, len = to - from;
165
+ let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
166
166
  return Object.assign(Object.assign({}, state.changeByRange(range => {
167
- if (range != main && len &&
168
- state.sliceDoc(range.from - len, range.from + to - main.from) != state.sliceDoc(from, to))
167
+ if (range != main && from != to &&
168
+ state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
169
169
  return { range };
170
170
  return {
171
- changes: { from: range.from - len, to: to == main.from ? range.to : range.from + to - main.from, insert: text },
172
- range: EditorSelection.cursor(range.from - len + text.length)
171
+ changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: text },
172
+ range: EditorSelection.cursor(range.from + fromOff + text.length)
173
173
  };
174
174
  })), { userEvent: "input.complete" });
175
175
  }
176
- function applyCompletion(view, option) {
177
- const apply = option.completion.apply || option.completion.label;
178
- let result = option.source;
179
- if (typeof apply == "string")
180
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
181
- else
182
- apply(view, option.completion, result.from, result.to);
183
- }
184
176
  const SourceCache = /*@__PURE__*/new WeakMap();
185
177
  function asSource(source) {
186
178
  if (!Array.isArray(source))
@@ -384,6 +376,232 @@ function defaultPositionInfo(view, list, option, info, space) {
384
376
  };
385
377
  }
386
378
 
379
+ /**
380
+ Returns a command that moves the completion selection forward or
381
+ backward by the given amount.
382
+ */
383
+ function moveCompletionSelection(forward, by = "option") {
384
+ return (view) => {
385
+ let cState = view.state.field(completionState, false);
386
+ if (!cState || !cState.open || cState.open.disabled ||
387
+ Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
388
+ return false;
389
+ let step = 1, tooltip;
390
+ if (by == "page" && (tooltip = getTooltip(view, cState.open.tooltip)))
391
+ step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
392
+ tooltip.dom.querySelector("li").offsetHeight) - 1);
393
+ let { length } = cState.open.options;
394
+ let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
395
+ if (selected < 0)
396
+ selected = by == "page" ? 0 : length - 1;
397
+ else if (selected >= length)
398
+ selected = by == "page" ? length - 1 : 0;
399
+ view.dispatch({ effects: setSelectedEffect.of(selected) });
400
+ return true;
401
+ };
402
+ }
403
+ /**
404
+ Accept the current completion.
405
+ */
406
+ const acceptCompletion = (view) => {
407
+ let cState = view.state.field(completionState, false);
408
+ if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
409
+ Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
410
+ return false;
411
+ if (!cState.open.disabled)
412
+ return applyCompletion(view, cState.open.options[cState.open.selected]);
413
+ return true;
414
+ };
415
+ /**
416
+ Explicitly start autocompletion.
417
+ */
418
+ const startCompletion = (view) => {
419
+ let cState = view.state.field(completionState, false);
420
+ if (!cState)
421
+ return false;
422
+ view.dispatch({ effects: startCompletionEffect.of(true) });
423
+ return true;
424
+ };
425
+ /**
426
+ Close the currently active completion.
427
+ */
428
+ const closeCompletion = (view) => {
429
+ let cState = view.state.field(completionState, false);
430
+ if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
431
+ return false;
432
+ view.dispatch({ effects: closeCompletionEffect.of(null) });
433
+ return true;
434
+ };
435
+ class RunningQuery {
436
+ constructor(active, context) {
437
+ this.active = active;
438
+ this.context = context;
439
+ this.time = Date.now();
440
+ this.updates = [];
441
+ // Note that 'undefined' means 'not done yet', whereas 'null' means
442
+ // 'query returned null'.
443
+ this.done = undefined;
444
+ }
445
+ }
446
+ const DebounceTime = 50, MaxUpdateCount = 50, MinAbortTime = 1000;
447
+ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
448
+ constructor(view) {
449
+ this.view = view;
450
+ this.debounceUpdate = -1;
451
+ this.running = [];
452
+ this.debounceAccept = -1;
453
+ this.composing = 0 /* CompositionState.None */;
454
+ for (let active of view.state.field(completionState).active)
455
+ if (active.state == 1 /* State.Pending */)
456
+ this.startQuery(active);
457
+ }
458
+ update(update) {
459
+ let cState = update.state.field(completionState);
460
+ if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
461
+ return;
462
+ let doesReset = update.transactions.some(tr => {
463
+ return (tr.selection || tr.docChanged) && !getUserEvent(tr);
464
+ });
465
+ for (let i = 0; i < this.running.length; i++) {
466
+ let query = this.running[i];
467
+ if (doesReset ||
468
+ query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
469
+ for (let handler of query.context.abortListeners) {
470
+ try {
471
+ handler();
472
+ }
473
+ catch (e) {
474
+ logException(this.view.state, e);
475
+ }
476
+ }
477
+ query.context.abortListeners = null;
478
+ this.running.splice(i--, 1);
479
+ }
480
+ else {
481
+ query.updates.push(...update.transactions);
482
+ }
483
+ }
484
+ if (this.debounceUpdate > -1)
485
+ clearTimeout(this.debounceUpdate);
486
+ this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
487
+ ? setTimeout(() => this.startUpdate(), DebounceTime) : -1;
488
+ if (this.composing != 0 /* CompositionState.None */)
489
+ for (let tr of update.transactions) {
490
+ if (getUserEvent(tr) == "input")
491
+ this.composing = 2 /* CompositionState.Changed */;
492
+ else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
493
+ this.composing = 3 /* CompositionState.ChangedAndMoved */;
494
+ }
495
+ }
496
+ startUpdate() {
497
+ this.debounceUpdate = -1;
498
+ let { state } = this.view, cState = state.field(completionState);
499
+ for (let active of cState.active) {
500
+ if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
501
+ this.startQuery(active);
502
+ }
503
+ }
504
+ startQuery(active) {
505
+ let { state } = this.view, pos = cur(state);
506
+ let context = new CompletionContext(state, pos, active.explicitPos == pos);
507
+ let pending = new RunningQuery(active, context);
508
+ this.running.push(pending);
509
+ Promise.resolve(active.source(context)).then(result => {
510
+ if (!pending.context.aborted) {
511
+ pending.done = result || null;
512
+ this.scheduleAccept();
513
+ }
514
+ }, err => {
515
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
516
+ logException(this.view.state, err);
517
+ });
518
+ }
519
+ scheduleAccept() {
520
+ if (this.running.every(q => q.done !== undefined))
521
+ this.accept();
522
+ else if (this.debounceAccept < 0)
523
+ this.debounceAccept = setTimeout(() => this.accept(), DebounceTime);
524
+ }
525
+ // For each finished query in this.running, try to create a result
526
+ // or, if appropriate, restart the query.
527
+ accept() {
528
+ var _a;
529
+ if (this.debounceAccept > -1)
530
+ clearTimeout(this.debounceAccept);
531
+ this.debounceAccept = -1;
532
+ let updated = [];
533
+ let conf = this.view.state.facet(completionConfig);
534
+ for (let i = 0; i < this.running.length; i++) {
535
+ let query = this.running[i];
536
+ if (query.done === undefined)
537
+ continue;
538
+ this.running.splice(i--, 1);
539
+ if (query.done) {
540
+ let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state));
541
+ // Replay the transactions that happened since the start of
542
+ // the request and see if that preserves the result
543
+ for (let tr of query.updates)
544
+ active = active.update(tr, conf);
545
+ if (active.hasResult()) {
546
+ updated.push(active);
547
+ continue;
548
+ }
549
+ }
550
+ let current = this.view.state.field(completionState).active.find(a => a.source == query.active.source);
551
+ if (current && current.state == 1 /* State.Pending */) {
552
+ if (query.done == null) {
553
+ // Explicitly failed. Should clear the pending status if it
554
+ // hasn't been re-set in the meantime.
555
+ let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
556
+ for (let tr of query.updates)
557
+ active = active.update(tr, conf);
558
+ if (active.state != 1 /* State.Pending */)
559
+ updated.push(active);
560
+ }
561
+ else {
562
+ // Cleared by subsequent transactions. Restart.
563
+ this.startQuery(current);
564
+ }
565
+ }
566
+ }
567
+ if (updated.length)
568
+ this.view.dispatch({ effects: setActiveEffect.of(updated) });
569
+ }
570
+ }, {
571
+ eventHandlers: {
572
+ blur(event) {
573
+ let state = this.view.state.field(completionState, false);
574
+ if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
575
+ let dialog = state.open && getTooltip(this.view, state.open.tooltip);
576
+ if (!dialog || !dialog.dom.contains(event.relatedTarget))
577
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
578
+ }
579
+ },
580
+ compositionstart() {
581
+ this.composing = 1 /* CompositionState.Started */;
582
+ },
583
+ compositionend() {
584
+ if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
585
+ // Safari fires compositionend events synchronously, possibly
586
+ // from inside an update, so dispatch asynchronously to avoid reentrancy
587
+ setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
588
+ }
589
+ this.composing = 0 /* CompositionState.None */;
590
+ }
591
+ }
592
+ });
593
+ function applyCompletion(view, option) {
594
+ const apply = option.completion.apply || option.completion.label;
595
+ let result = view.state.field(completionState).active.find(a => a.source == option.source);
596
+ if (!(result instanceof ActiveResult))
597
+ return false;
598
+ if (typeof apply == "string")
599
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
600
+ else
601
+ apply(view, option.completion, result.from, result.to);
602
+ return true;
603
+ }
604
+
387
605
  function optionContent(config) {
388
606
  let content = config.addToOptions.slice();
389
607
  if (config.icons)
@@ -689,14 +907,14 @@ function sortOptions(active, state) {
689
907
  if (getMatch)
690
908
  for (let n of getMatch(option))
691
909
  match.push(n);
692
- addOption(new Option(option, a, match, match[0]));
910
+ addOption(new Option(option, a.source, match, match[0]));
693
911
  }
694
912
  }
695
913
  else {
696
914
  let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
697
915
  for (let option of a.result.options)
698
916
  if (match = matcher.match(option.label)) {
699
- addOption(new Option(option, a, match, match[0] + (option.boost || 0)));
917
+ addOption(new Option(option, a.source, match, match[0] + (option.boost || 0)));
700
918
  }
701
919
  }
702
920
  }
@@ -922,221 +1140,6 @@ const completionState = /*@__PURE__*/StateField.define({
922
1140
  ]
923
1141
  });
924
1142
 
925
- /**
926
- Returns a command that moves the completion selection forward or
927
- backward by the given amount.
928
- */
929
- function moveCompletionSelection(forward, by = "option") {
930
- return (view) => {
931
- let cState = view.state.field(completionState, false);
932
- if (!cState || !cState.open || cState.open.disabled ||
933
- Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
934
- return false;
935
- let step = 1, tooltip;
936
- if (by == "page" && (tooltip = getTooltip(view, cState.open.tooltip)))
937
- step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
938
- tooltip.dom.querySelector("li").offsetHeight) - 1);
939
- let { length } = cState.open.options;
940
- let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
941
- if (selected < 0)
942
- selected = by == "page" ? 0 : length - 1;
943
- else if (selected >= length)
944
- selected = by == "page" ? length - 1 : 0;
945
- view.dispatch({ effects: setSelectedEffect.of(selected) });
946
- return true;
947
- };
948
- }
949
- /**
950
- Accept the current completion.
951
- */
952
- const acceptCompletion = (view) => {
953
- let cState = view.state.field(completionState, false);
954
- if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
955
- Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
956
- return false;
957
- if (!cState.open.disabled)
958
- applyCompletion(view, cState.open.options[cState.open.selected]);
959
- return true;
960
- };
961
- /**
962
- Explicitly start autocompletion.
963
- */
964
- const startCompletion = (view) => {
965
- let cState = view.state.field(completionState, false);
966
- if (!cState)
967
- return false;
968
- view.dispatch({ effects: startCompletionEffect.of(true) });
969
- return true;
970
- };
971
- /**
972
- Close the currently active completion.
973
- */
974
- const closeCompletion = (view) => {
975
- let cState = view.state.field(completionState, false);
976
- if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
977
- return false;
978
- view.dispatch({ effects: closeCompletionEffect.of(null) });
979
- return true;
980
- };
981
- class RunningQuery {
982
- constructor(active, context) {
983
- this.active = active;
984
- this.context = context;
985
- this.time = Date.now();
986
- this.updates = [];
987
- // Note that 'undefined' means 'not done yet', whereas 'null' means
988
- // 'query returned null'.
989
- this.done = undefined;
990
- }
991
- }
992
- const DebounceTime = 50, MaxUpdateCount = 50, MinAbortTime = 1000;
993
- const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
994
- constructor(view) {
995
- this.view = view;
996
- this.debounceUpdate = -1;
997
- this.running = [];
998
- this.debounceAccept = -1;
999
- this.composing = 0 /* CompositionState.None */;
1000
- for (let active of view.state.field(completionState).active)
1001
- if (active.state == 1 /* State.Pending */)
1002
- this.startQuery(active);
1003
- }
1004
- update(update) {
1005
- let cState = update.state.field(completionState);
1006
- if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
1007
- return;
1008
- let doesReset = update.transactions.some(tr => {
1009
- return (tr.selection || tr.docChanged) && !getUserEvent(tr);
1010
- });
1011
- for (let i = 0; i < this.running.length; i++) {
1012
- let query = this.running[i];
1013
- if (doesReset ||
1014
- query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
1015
- for (let handler of query.context.abortListeners) {
1016
- try {
1017
- handler();
1018
- }
1019
- catch (e) {
1020
- logException(this.view.state, e);
1021
- }
1022
- }
1023
- query.context.abortListeners = null;
1024
- this.running.splice(i--, 1);
1025
- }
1026
- else {
1027
- query.updates.push(...update.transactions);
1028
- }
1029
- }
1030
- if (this.debounceUpdate > -1)
1031
- clearTimeout(this.debounceUpdate);
1032
- this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
1033
- ? setTimeout(() => this.startUpdate(), DebounceTime) : -1;
1034
- if (this.composing != 0 /* CompositionState.None */)
1035
- for (let tr of update.transactions) {
1036
- if (getUserEvent(tr) == "input")
1037
- this.composing = 2 /* CompositionState.Changed */;
1038
- else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
1039
- this.composing = 3 /* CompositionState.ChangedAndMoved */;
1040
- }
1041
- }
1042
- startUpdate() {
1043
- this.debounceUpdate = -1;
1044
- let { state } = this.view, cState = state.field(completionState);
1045
- for (let active of cState.active) {
1046
- if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
1047
- this.startQuery(active);
1048
- }
1049
- }
1050
- startQuery(active) {
1051
- let { state } = this.view, pos = cur(state);
1052
- let context = new CompletionContext(state, pos, active.explicitPos == pos);
1053
- let pending = new RunningQuery(active, context);
1054
- this.running.push(pending);
1055
- Promise.resolve(active.source(context)).then(result => {
1056
- if (!pending.context.aborted) {
1057
- pending.done = result || null;
1058
- this.scheduleAccept();
1059
- }
1060
- }, err => {
1061
- this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1062
- logException(this.view.state, err);
1063
- });
1064
- }
1065
- scheduleAccept() {
1066
- if (this.running.every(q => q.done !== undefined))
1067
- this.accept();
1068
- else if (this.debounceAccept < 0)
1069
- this.debounceAccept = setTimeout(() => this.accept(), DebounceTime);
1070
- }
1071
- // For each finished query in this.running, try to create a result
1072
- // or, if appropriate, restart the query.
1073
- accept() {
1074
- var _a;
1075
- if (this.debounceAccept > -1)
1076
- clearTimeout(this.debounceAccept);
1077
- this.debounceAccept = -1;
1078
- let updated = [];
1079
- let conf = this.view.state.facet(completionConfig);
1080
- for (let i = 0; i < this.running.length; i++) {
1081
- let query = this.running[i];
1082
- if (query.done === undefined)
1083
- continue;
1084
- this.running.splice(i--, 1);
1085
- if (query.done) {
1086
- let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state));
1087
- // Replay the transactions that happened since the start of
1088
- // the request and see if that preserves the result
1089
- for (let tr of query.updates)
1090
- active = active.update(tr, conf);
1091
- if (active.hasResult()) {
1092
- updated.push(active);
1093
- continue;
1094
- }
1095
- }
1096
- let current = this.view.state.field(completionState).active.find(a => a.source == query.active.source);
1097
- if (current && current.state == 1 /* State.Pending */) {
1098
- if (query.done == null) {
1099
- // Explicitly failed. Should clear the pending status if it
1100
- // hasn't been re-set in the meantime.
1101
- let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
1102
- for (let tr of query.updates)
1103
- active = active.update(tr, conf);
1104
- if (active.state != 1 /* State.Pending */)
1105
- updated.push(active);
1106
- }
1107
- else {
1108
- // Cleared by subsequent transactions. Restart.
1109
- this.startQuery(current);
1110
- }
1111
- }
1112
- }
1113
- if (updated.length)
1114
- this.view.dispatch({ effects: setActiveEffect.of(updated) });
1115
- }
1116
- }, {
1117
- eventHandlers: {
1118
- blur(event) {
1119
- let state = this.view.state.field(completionState, false);
1120
- if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
1121
- let dialog = state.open && getTooltip(this.view, state.open.tooltip);
1122
- if (!dialog || !dialog.dom.contains(event.relatedTarget))
1123
- this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1124
- }
1125
- },
1126
- compositionstart() {
1127
- this.composing = 1 /* CompositionState.Started */;
1128
- },
1129
- compositionend() {
1130
- if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
1131
- // Safari fires compositionend events synchronously, possibly
1132
- // from inside an update, so dispatch asynchronously to avoid reentrancy
1133
- setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
1134
- }
1135
- this.composing = 0 /* CompositionState.None */;
1136
- }
1137
- }
1138
- });
1139
-
1140
1143
  const baseTheme = /*@__PURE__*/EditorView.baseTheme({
1141
1144
  ".cm-tooltip.cm-tooltip-autocomplete": {
1142
1145
  "& > ul": {
@@ -1428,7 +1431,7 @@ function snippet(template) {
1428
1431
  let spec = {
1429
1432
  changes: { from, to, insert: Text.of(text) },
1430
1433
  scrollIntoView: true,
1431
- annotations: pickedCompletion.of(completion)
1434
+ annotations: completion ? pickedCompletion.of(completion) : undefined
1432
1435
  };
1433
1436
  if (ranges.length)
1434
1437
  spec.selection = fieldSelection(ranges, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.5.1",
3
+ "version": "6.6.1",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",