@codemirror/autocomplete 6.6.0 → 6.7.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.7.0 (2023-05-11)
2
+
3
+ ### New features
4
+
5
+ The new `hasNextSnippetField` and `hasPrevSnippetField` functions can be used to figure out if the snippet-field-motion commands apply to a given state.
6
+
7
+ ## 6.6.1 (2023-05-03)
8
+
9
+ ### Bug fixes
10
+
11
+ 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.
12
+
1
13
  ## 6.6.0 (2023-04-27)
2
14
 
3
15
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -177,14 +177,6 @@ function insertCompletionText(state$1, text, from, to) {
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": {
@@ -1476,6 +1479,22 @@ const nextSnippetField = moveField(1);
1476
1479
  Move to the previous snippet field, if available.
1477
1480
  */
1478
1481
  const prevSnippetField = moveField(-1);
1482
+ /**
1483
+ Check if there is an active snippet with a next field for
1484
+ `nextSnippetField` to move to.
1485
+ */
1486
+ function hasNextSnippetField(state) {
1487
+ let active = state.field(snippetState, false);
1488
+ return !!(active && active.ranges.some(r => r.field == active.active + 1));
1489
+ }
1490
+ /**
1491
+ Returns true if there is an active snippet and a previous field
1492
+ for `prevSnippetField` to move to.
1493
+ */
1494
+ function hasPrevSnippetField(state) {
1495
+ let active = state.field(snippetState, false);
1496
+ return !!(active && active.active > 0);
1497
+ }
1479
1498
  const defaultSnippetKeymap = [
1480
1499
  { key: "Tab", run: nextSnippetField, shift: prevSnippetField },
1481
1500
  { key: "Escape", run: clearSnippet }
@@ -1935,6 +1954,8 @@ exports.completionKeymap = completionKeymap;
1935
1954
  exports.completionStatus = completionStatus;
1936
1955
  exports.currentCompletions = currentCompletions;
1937
1956
  exports.deleteBracketPair = deleteBracketPair;
1957
+ exports.hasNextSnippetField = hasNextSnippetField;
1958
+ exports.hasPrevSnippetField = hasPrevSnippetField;
1938
1959
  exports.ifIn = ifIn;
1939
1960
  exports.ifNotIn = ifNotIn;
1940
1961
  exports.insertBracket = insertBracket;
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
@@ -398,6 +398,16 @@ Move to the previous snippet field, if available.
398
398
  */
399
399
  declare const prevSnippetField: StateCommand;
400
400
  /**
401
+ Check if there is an active snippet with a next field for
402
+ `nextSnippetField` to move to.
403
+ */
404
+ declare function hasNextSnippetField(state: EditorState): boolean;
405
+ /**
406
+ Returns true if there is an active snippet and a previous field
407
+ for `prevSnippetField` to move to.
408
+ */
409
+ declare function hasPrevSnippetField(state: EditorState): boolean;
410
+ /**
401
411
  A facet that can be used to configure the key bindings used by
402
412
  snippets. The default binds Tab to
403
413
  [`nextSnippetField`](https://codemirror.net/6/docs/ref/#autocomplete.nextSnippetField), Shift-Tab to
@@ -534,4 +544,4 @@ the currently selected completion.
534
544
  */
535
545
  declare function setSelectedCompletion(index: number): StateEffect<unknown>;
536
546
 
537
- export { CloseBracketConfig, Completion, CompletionContext, CompletionResult, CompletionSection, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
547
+ export { CloseBracketConfig, Completion, CompletionContext, 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 };
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
  /**
@@ -173,14 +173,6 @@ function insertCompletionText(state, text, from, to) {
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": {
@@ -1472,6 +1475,22 @@ const nextSnippetField = /*@__PURE__*/moveField(1);
1472
1475
  Move to the previous snippet field, if available.
1473
1476
  */
1474
1477
  const prevSnippetField = /*@__PURE__*/moveField(-1);
1478
+ /**
1479
+ Check if there is an active snippet with a next field for
1480
+ `nextSnippetField` to move to.
1481
+ */
1482
+ function hasNextSnippetField(state) {
1483
+ let active = state.field(snippetState, false);
1484
+ return !!(active && active.ranges.some(r => r.field == active.active + 1));
1485
+ }
1486
+ /**
1487
+ Returns true if there is an active snippet and a previous field
1488
+ for `prevSnippetField` to move to.
1489
+ */
1490
+ function hasPrevSnippetField(state) {
1491
+ let active = state.field(snippetState, false);
1492
+ return !!(active && active.active > 0);
1493
+ }
1475
1494
  const defaultSnippetKeymap = [
1476
1495
  { key: "Tab", run: nextSnippetField, shift: prevSnippetField },
1477
1496
  { key: "Escape", run: clearSnippet }
@@ -1918,4 +1937,4 @@ function setSelectedCompletion(index) {
1918
1937
  return setSelectedEffect.of(index);
1919
1938
  }
1920
1939
 
1921
- export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
1940
+ export { CompletionContext, 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.6.0",
3
+ "version": "6.7.0",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",