@codemirror/autocomplete 0.19.1 → 0.19.5

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,45 @@
1
+ ## 0.19.5 (2021-11-09)
2
+
3
+ ### Bug fixes
4
+
5
+ Make sure info tooltips don't stick out of the bottom of the page.
6
+
7
+ ### New features
8
+
9
+ The package exports a new function `selectedCompletion`, which can be used to find out which completion is currently selected.
10
+
11
+ Transactions created by picking a completion now have an annotation (`pickedCompletion`) holding the original completion.
12
+
13
+ ## 0.19.4 (2021-10-24)
14
+
15
+ ### Bug fixes
16
+
17
+ Don't rely on the platform's highlight colors for the active completion, since those are inconsistent and may not be appropriate for the theme.
18
+
19
+ Fix incorrect match underline for some kinds of matched completions.
20
+
21
+ ## 0.19.3 (2021-08-31)
22
+
23
+ ### Bug fixes
24
+
25
+ Improve the sorting of completions by using `localeCompare`.
26
+
27
+ Fix reading of autocompletions in NVDA screen reader.
28
+
29
+ ### New features
30
+
31
+ The new `icons` option can be used to turn off icons in the completion list.
32
+
33
+ The `optionClass` option can now be used to add CSS classes to the options in the completion list.
34
+
35
+ It is now possible to inject additional content into rendered completion options with the `addToOptions` configuration option.
36
+
37
+ ## 0.19.2 (2021-08-25)
38
+
39
+ ### Bug fixes
40
+
41
+ Fix an issue where `completeAnyWord` would return results when there was no query and `explicit` was false.
42
+
1
43
  ## 0.19.1 (2021-08-11)
2
44
 
3
45
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -150,6 +150,11 @@ function ensureAnchor(expr, start) {
150
150
  return expr;
151
151
  return new RegExp(`${addStart ? "^" : ""}(?:${source})${addEnd ? "$" : ""}`, (_a = expr.flags) !== null && _a !== void 0 ? _a : (expr.ignoreCase ? "i" : ""));
152
152
  }
153
+ /**
154
+ This annotation is added to transactions that are produced by
155
+ picking a completion.
156
+ */
157
+ const pickedCompletion = state.Annotation.define();
153
158
  function applyCompletion(view, option) {
154
159
  let apply = option.completion.apply || option.completion.label;
155
160
  let result = option.source;
@@ -157,7 +162,8 @@ function applyCompletion(view, option) {
157
162
  view.dispatch({
158
163
  changes: { from: result.from, to: result.to, insert: apply },
159
164
  selection: { anchor: result.from + apply.length },
160
- userEvent: "input.complete"
165
+ userEvent: "input.complete",
166
+ annotations: pickedCompletion.of(option.completion)
161
167
  });
162
168
  }
163
169
  else {
@@ -251,7 +257,7 @@ class FuzzyMatcher {
251
257
  if (next == chars[adjacentTo] || next == folded[adjacentTo]) {
252
258
  if (adjacentTo == 0)
253
259
  adjacentStart = i;
254
- adjacentEnd = i;
260
+ adjacentEnd = i + 1;
255
261
  adjacentTo++;
256
262
  }
257
263
  else {
@@ -301,152 +307,67 @@ const completionConfig = state.Facet.define({
301
307
  activateOnTyping: true,
302
308
  override: null,
303
309
  maxRenderedOptions: 100,
304
- defaultKeymap: true
310
+ defaultKeymap: true,
311
+ optionClass: () => "",
312
+ icons: true,
313
+ addToOptions: []
305
314
  }, {
306
- defaultKeymap: (a, b) => a && b
315
+ defaultKeymap: (a, b) => a && b,
316
+ icons: (a, b) => a && b,
317
+ optionClass: (a, b) => c => joinClass(a(c), b(c)),
318
+ addToOptions: (a, b) => a.concat(b)
307
319
  });
308
320
  }
309
321
  });
322
+ function joinClass(a, b) {
323
+ return a ? b ? a + " " + b : a : b;
324
+ }
310
325
 
311
- const MaxInfoWidth = 300;
312
- const baseTheme = view.EditorView.baseTheme({
313
- ".cm-tooltip.cm-tooltip-autocomplete": {
314
- "& > ul": {
315
- fontFamily: "monospace",
316
- whiteSpace: "nowrap",
317
- overflow: "auto",
318
- maxWidth_fallback: "700px",
319
- maxWidth: "min(700px, 95vw)",
320
- maxHeight: "10em",
321
- listStyle: "none",
322
- margin: 0,
323
- padding: 0,
324
- "& > li": {
325
- cursor: "pointer",
326
- padding: "1px 1em 1px 3px",
327
- lineHeight: 1.2
326
+ function optionContent(config) {
327
+ let content = config.addToOptions.slice();
328
+ if (config.icons)
329
+ content.push({
330
+ render(completion) {
331
+ let icon = document.createElement("div");
332
+ icon.classList.add("cm-completionIcon");
333
+ if (completion.type)
334
+ icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls));
335
+ icon.setAttribute("aria-hidden", "true");
336
+ return icon;
328
337
  },
329
- "& > li[aria-selected]": {
330
- background_fallback: "#bdf",
331
- backgroundColor: "Highlight",
332
- color_fallback: "white",
333
- color: "HighlightText"
338
+ position: 20
339
+ });
340
+ content.push({
341
+ render(completion, _s, match) {
342
+ let labelElt = document.createElement("span");
343
+ labelElt.className = "cm-completionLabel";
344
+ let { label } = completion, off = 0;
345
+ for (let j = 1; j < match.length;) {
346
+ let from = match[j++], to = match[j++];
347
+ if (from > off)
348
+ labelElt.appendChild(document.createTextNode(label.slice(off, from)));
349
+ let span = labelElt.appendChild(document.createElement("span"));
350
+ span.appendChild(document.createTextNode(label.slice(from, to)));
351
+ span.className = "cm-completionMatchedText";
352
+ off = to;
334
353
  }
335
- }
336
- },
337
- ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
338
- content: '"···"',
339
- opacity: 0.5,
340
- display: "block",
341
- textAlign: "center"
342
- },
343
- ".cm-tooltip.cm-completionInfo": {
344
- position: "absolute",
345
- padding: "3px 9px",
346
- width: "max-content",
347
- maxWidth: MaxInfoWidth + "px",
348
- },
349
- ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
350
- ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
351
- "&light .cm-snippetField": { backgroundColor: "#00000022" },
352
- "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
353
- ".cm-snippetFieldPosition": {
354
- verticalAlign: "text-top",
355
- width: 0,
356
- height: "1.15em",
357
- margin: "0 -0.7px -.7em",
358
- borderLeft: "1.4px dotted #888"
359
- },
360
- ".cm-completionMatchedText": {
361
- textDecoration: "underline"
362
- },
363
- ".cm-completionDetail": {
364
- marginLeft: "0.5em",
365
- fontStyle: "italic"
366
- },
367
- ".cm-completionIcon": {
368
- fontSize: "90%",
369
- width: ".8em",
370
- display: "inline-block",
371
- textAlign: "center",
372
- paddingRight: ".6em",
373
- opacity: "0.6"
374
- },
375
- ".cm-completionIcon-function, .cm-completionIcon-method": {
376
- "&:after": { content: "'ƒ'" }
377
- },
378
- ".cm-completionIcon-class": {
379
- "&:after": { content: "'○'" }
380
- },
381
- ".cm-completionIcon-interface": {
382
- "&:after": { content: "'◌'" }
383
- },
384
- ".cm-completionIcon-variable": {
385
- "&:after": { content: "'𝑥'" }
386
- },
387
- ".cm-completionIcon-constant": {
388
- "&:after": { content: "'𝐶'" }
389
- },
390
- ".cm-completionIcon-type": {
391
- "&:after": { content: "'𝑡'" }
392
- },
393
- ".cm-completionIcon-enum": {
394
- "&:after": { content: "'∪'" }
395
- },
396
- ".cm-completionIcon-property": {
397
- "&:after": { content: "'□'" }
398
- },
399
- ".cm-completionIcon-keyword": {
400
- "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
401
- },
402
- ".cm-completionIcon-namespace": {
403
- "&:after": { content: "'▢'" }
404
- },
405
- ".cm-completionIcon-text": {
406
- "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
407
- }
408
- });
409
-
410
- function createListBox(options, id, range) {
411
- const ul = document.createElement("ul");
412
- ul.id = id;
413
- ul.setAttribute("role", "listbox");
414
- ul.setAttribute("aria-expanded", "true");
415
- for (let i = range.from; i < range.to; i++) {
416
- let { completion, match } = options[i];
417
- const li = ul.appendChild(document.createElement("li"));
418
- li.id = id + "-" + i;
419
- let icon = li.appendChild(document.createElement("div"));
420
- icon.classList.add("cm-completionIcon");
421
- if (completion.type)
422
- icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls));
423
- icon.setAttribute("aria-hidden", "true");
424
- let labelElt = li.appendChild(document.createElement("span"));
425
- labelElt.className = "cm-completionLabel";
426
- let { label, detail } = completion, off = 0;
427
- for (let j = 1; j < match.length;) {
428
- let from = match[j++], to = match[j++];
429
- if (from > off)
430
- labelElt.appendChild(document.createTextNode(label.slice(off, from)));
431
- let span = labelElt.appendChild(document.createElement("span"));
432
- span.appendChild(document.createTextNode(label.slice(from, to)));
433
- span.className = "cm-completionMatchedText";
434
- off = to;
435
- }
436
- if (off < label.length)
437
- labelElt.appendChild(document.createTextNode(label.slice(off)));
438
- if (detail) {
439
- let detailElt = li.appendChild(document.createElement("span"));
354
+ if (off < label.length)
355
+ labelElt.appendChild(document.createTextNode(label.slice(off)));
356
+ return labelElt;
357
+ },
358
+ position: 50
359
+ }, {
360
+ render(completion) {
361
+ if (!completion.detail)
362
+ return null;
363
+ let detailElt = document.createElement("span");
440
364
  detailElt.className = "cm-completionDetail";
441
- detailElt.textContent = detail;
442
- }
443
- li.setAttribute("role", "option");
444
- }
445
- if (range.from)
446
- ul.classList.add("cm-completionListIncompleteTop");
447
- if (range.to < options.length)
448
- ul.classList.add("cm-completionListIncompleteBottom");
449
- return ul;
365
+ detailElt.textContent = completion.detail;
366
+ return detailElt;
367
+ },
368
+ position: 80
369
+ });
370
+ return content.sort((a, b) => a.position - b.position).map(a => a.render);
450
371
  }
451
372
  function createInfoDialog(option, view$1) {
452
373
  let dom = document.createElement("div");
@@ -487,6 +408,8 @@ class CompletionTooltip {
487
408
  let cState = view.state.field(stateField);
488
409
  let { options, selected } = cState.open;
489
410
  let config = view.state.facet(completionConfig);
411
+ this.optionContent = optionContent(config);
412
+ this.optionClass = config.optionClass;
490
413
  this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
491
414
  this.dom = document.createElement("div");
492
415
  this.dom.className = "cm-tooltip-autocomplete";
@@ -499,7 +422,7 @@ class CompletionTooltip {
499
422
  }
500
423
  }
501
424
  });
502
- this.list = this.dom.appendChild(createListBox(options, cState.id, this.range));
425
+ this.list = this.dom.appendChild(this.createListBox(options, cState.id, this.range));
503
426
  this.list.addEventListener("scroll", () => {
504
427
  if (this.info)
505
428
  this.view.requestMeasure(this.placeInfo);
@@ -519,7 +442,7 @@ class CompletionTooltip {
519
442
  if (open.selected < this.range.from || open.selected >= this.range.to) {
520
443
  this.range = rangeAroundSelected(open.options.length, open.selected, this.view.state.facet(completionConfig).maxRenderedOptions);
521
444
  this.list.remove();
522
- this.list = this.dom.appendChild(createListBox(open.options, cState.id, this.range));
445
+ this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range));
523
446
  this.list.addEventListener("scroll", () => {
524
447
  if (this.info)
525
448
  this.view.requestMeasure(this.placeInfo);
@@ -557,17 +480,17 @@ class CompletionTooltip {
557
480
  }
558
481
  measureInfo() {
559
482
  let sel = this.dom.querySelector("[aria-selected]");
560
- if (!sel)
483
+ if (!sel || !this.info)
561
484
  return null;
562
- let rect = this.dom.getBoundingClientRect();
563
- let top = sel.getBoundingClientRect().top - rect.top;
485
+ let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
486
+ let top = Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height) - rect.top;
564
487
  if (top < 0 || top > this.list.clientHeight - 10)
565
488
  return null;
566
489
  let left = this.view.textDirection == view.Direction.RTL;
567
490
  let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
568
- if (left && spaceLeft < Math.min(MaxInfoWidth, spaceRight))
491
+ if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
569
492
  left = false;
570
- else if (!left && spaceRight < Math.min(MaxInfoWidth, spaceLeft))
493
+ else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
571
494
  left = true;
572
495
  return { top, left };
573
496
  }
@@ -578,6 +501,30 @@ class CompletionTooltip {
578
501
  this.info.classList.toggle("cm-completionInfo-right", !pos.left);
579
502
  }
580
503
  }
504
+ createListBox(options, id, range) {
505
+ const ul = document.createElement("ul");
506
+ ul.id = id;
507
+ ul.setAttribute("role", "listbox");
508
+ for (let i = range.from; i < range.to; i++) {
509
+ let { completion, match } = options[i];
510
+ const li = ul.appendChild(document.createElement("li"));
511
+ li.id = id + "-" + i;
512
+ li.setAttribute("role", "option");
513
+ let cls = this.optionClass(completion);
514
+ if (cls)
515
+ li.className = cls;
516
+ for (let source of this.optionContent) {
517
+ let node = source(completion, this.view.state, match);
518
+ if (node)
519
+ li.appendChild(node);
520
+ }
521
+ }
522
+ if (range.from)
523
+ ul.classList.add("cm-completionListIncompleteTop");
524
+ if (range.to < options.length)
525
+ ul.classList.add("cm-completionListIncompleteBottom");
526
+ return ul;
527
+ }
581
528
  }
582
529
  // We allocate a new function instance every time the completion
583
530
  // changes to force redrawing/repositioning of the tooltip
@@ -712,20 +659,24 @@ function sameResults(a, b) {
712
659
  return false;
713
660
  }
714
661
  }
662
+ const baseAttrs = {
663
+ "aria-autocomplete": "list",
664
+ "aria-expanded": "false"
665
+ };
715
666
  function makeAttrs(id, selected) {
716
667
  return {
717
668
  "aria-autocomplete": "list",
669
+ "aria-expanded": "true",
718
670
  "aria-activedescendant": id + "-" + selected,
719
- "aria-owns": id
671
+ "aria-controls": id
720
672
  };
721
673
  }
722
- const baseAttrs = { "aria-autocomplete": "list" }, none = [];
674
+ const none = [];
723
675
  function cmpOption(a, b) {
724
676
  let dScore = b.match[0] - a.match[0];
725
677
  if (dScore)
726
678
  return dScore;
727
- let lA = a.completion.label, lB = b.completion.label;
728
- return lA < lB ? -1 : lA == lB ? 0 : 1;
679
+ return a.completion.label.localeCompare(b.completion.label);
729
680
  }
730
681
  function getUserEvent(tr) {
731
682
  return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
@@ -836,7 +787,7 @@ Accept the current completion.
836
787
  */
837
788
  const acceptCompletion = (view) => {
838
789
  let cState = view.state.field(completionState, false);
839
- if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
790
+ if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
840
791
  return false;
841
792
  applyCompletion(view, cState.open.options[cState.open.selected]);
842
793
  return true;
@@ -1012,6 +963,106 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1012
963
  }
1013
964
  });
1014
965
 
966
+ const baseTheme = view.EditorView.baseTheme({
967
+ ".cm-tooltip.cm-tooltip-autocomplete": {
968
+ "& > ul": {
969
+ fontFamily: "monospace",
970
+ whiteSpace: "nowrap",
971
+ overflow: "auto",
972
+ maxWidth_fallback: "700px",
973
+ maxWidth: "min(700px, 95vw)",
974
+ maxHeight: "10em",
975
+ listStyle: "none",
976
+ margin: 0,
977
+ padding: 0,
978
+ "& > li": {
979
+ cursor: "pointer",
980
+ padding: "1px 1em 1px 3px",
981
+ lineHeight: 1.2
982
+ },
983
+ }
984
+ },
985
+ "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
986
+ background: "#39e",
987
+ color: "white",
988
+ },
989
+ "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
990
+ background: "#347",
991
+ color: "white",
992
+ },
993
+ ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
994
+ content: '"···"',
995
+ opacity: 0.5,
996
+ display: "block",
997
+ textAlign: "center"
998
+ },
999
+ ".cm-tooltip.cm-completionInfo": {
1000
+ position: "absolute",
1001
+ padding: "3px 9px",
1002
+ width: "max-content",
1003
+ maxWidth: "300px",
1004
+ },
1005
+ ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
1006
+ ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
1007
+ "&light .cm-snippetField": { backgroundColor: "#00000022" },
1008
+ "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
1009
+ ".cm-snippetFieldPosition": {
1010
+ verticalAlign: "text-top",
1011
+ width: 0,
1012
+ height: "1.15em",
1013
+ margin: "0 -0.7px -.7em",
1014
+ borderLeft: "1.4px dotted #888"
1015
+ },
1016
+ ".cm-completionMatchedText": {
1017
+ textDecoration: "underline"
1018
+ },
1019
+ ".cm-completionDetail": {
1020
+ marginLeft: "0.5em",
1021
+ fontStyle: "italic"
1022
+ },
1023
+ ".cm-completionIcon": {
1024
+ fontSize: "90%",
1025
+ width: ".8em",
1026
+ display: "inline-block",
1027
+ textAlign: "center",
1028
+ paddingRight: ".6em",
1029
+ opacity: "0.6"
1030
+ },
1031
+ ".cm-completionIcon-function, .cm-completionIcon-method": {
1032
+ "&:after": { content: "'ƒ'" }
1033
+ },
1034
+ ".cm-completionIcon-class": {
1035
+ "&:after": { content: "'○'" }
1036
+ },
1037
+ ".cm-completionIcon-interface": {
1038
+ "&:after": { content: "'◌'" }
1039
+ },
1040
+ ".cm-completionIcon-variable": {
1041
+ "&:after": { content: "'𝑥'" }
1042
+ },
1043
+ ".cm-completionIcon-constant": {
1044
+ "&:after": { content: "'𝐶'" }
1045
+ },
1046
+ ".cm-completionIcon-type": {
1047
+ "&:after": { content: "'𝑡'" }
1048
+ },
1049
+ ".cm-completionIcon-enum": {
1050
+ "&:after": { content: "'∪'" }
1051
+ },
1052
+ ".cm-completionIcon-property": {
1053
+ "&:after": { content: "'□'" }
1054
+ },
1055
+ ".cm-completionIcon-keyword": {
1056
+ "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
1057
+ },
1058
+ ".cm-completionIcon-namespace": {
1059
+ "&:after": { content: "'▢'" }
1060
+ },
1061
+ ".cm-completionIcon-text": {
1062
+ "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
1063
+ }
1064
+ });
1065
+
1015
1066
  class FieldPos {
1016
1067
  constructor(field, line, from, to) {
1017
1068
  this.field = field;
@@ -1162,8 +1213,7 @@ function snippet(template) {
1162
1213
  let active = new ActiveSnippet(ranges, 0);
1163
1214
  let effects = spec.effects = [setActive.of(active)];
1164
1215
  if (editor.state.field(snippetState, false) === undefined)
1165
- effects.push(state.StateEffect.appendConfig.of([snippetState.init(() => active), addSnippetKeymap,
1166
- snippetPointerHandler, baseTheme]));
1216
+ effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
1167
1217
  }
1168
1218
  editor.dispatch(editor.state.update(spec));
1169
1219
  };
@@ -1213,7 +1263,7 @@ to [`clearSnippet`](https://codemirror.net/6/docs/ref/#autocomplete.clearSnippet
1213
1263
  const snippetKeymap = state.Facet.define({
1214
1264
  combine(maps) { return maps.length ? maps[0] : defaultSnippetKeymap; }
1215
1265
  });
1216
- const addSnippetKeymap = state.Prec.override(view.keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1266
+ const addSnippetKeymap = state.Prec.highest(view.keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1217
1267
  /**
1218
1268
  Create a completion from a snippet. Returns an object with the
1219
1269
  properties from `completion`, plus an `apply` function that
@@ -1238,42 +1288,81 @@ const snippetPointerHandler = view.EditorView.domEventHandlers({
1238
1288
  }
1239
1289
  });
1240
1290
 
1241
- /**
1242
- A completion source that will scan the document for words (using a
1243
- [character categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer)), and
1244
- return those as completions.
1245
- */
1246
- const completeAnyWord = context => {
1247
- let options = [], seen = Object.create(null);
1248
- let cat = context.state.charCategorizer(context.pos);
1249
- let start = Math.max(0, context.pos - 50000 /* Range */), end = Math.min(context.state.doc.length, start + 50000 /* Range */ * 2);
1250
- let from = context.pos;
1251
- for (let cur = context.state.doc.iterRange(start, end), pos = start; !(cur.next()).done;) {
1252
- let { value } = cur, start = -1;
1253
- for (let i = 0;; i++) {
1254
- if (i < value.length && cat(value[i]) == state.CharCategory.Word) {
1255
- if (start < 0)
1256
- start = i;
1291
+ function wordRE(wordChars) {
1292
+ let escaped = wordChars.replace(/[\\[.+*?(){|^$]/g, "\\$&");
1293
+ try {
1294
+ return new RegExp(`[\\p{Alphabetic}\\p{Number}_${escaped}]+`, "ug");
1295
+ }
1296
+ catch (_a) {
1297
+ return new RegExp(`[\w${escaped}]`, "g");
1298
+ }
1299
+ }
1300
+ function mapRE(re, f) {
1301
+ return new RegExp(f(re.source), re.unicode ? "u" : "");
1302
+ }
1303
+ const wordCaches = Object.create(null);
1304
+ function wordCache(wordChars) {
1305
+ return wordCaches[wordChars] || (wordCaches[wordChars] = new WeakMap);
1306
+ }
1307
+ function storeWords(doc, wordRE, result, seen, ignoreAt) {
1308
+ for (let lines = doc.iterLines(), pos = 0; !lines.next().done;) {
1309
+ let { value } = lines, m;
1310
+ wordRE.lastIndex = 0;
1311
+ while (m = wordRE.exec(value)) {
1312
+ if (!seen[m[0]] && pos + m.index != ignoreAt) {
1313
+ result.push({ type: "text", label: m[0] });
1314
+ seen[m[0]] = true;
1315
+ if (result.length >= 2000 /* MaxList */)
1316
+ return;
1257
1317
  }
1258
- else if (start > -1) {
1259
- if (pos + start <= context.pos && pos + i >= context.pos) {
1260
- from = pos + start;
1261
- }
1262
- else {
1263
- let word = value.slice(start, i);
1264
- if (!seen[word]) {
1265
- options.push({ type: "text", label: word });
1266
- seen[word] = true;
1318
+ }
1319
+ pos += value.length + 1;
1320
+ }
1321
+ }
1322
+ function collectWords(doc, cache, wordRE, to, ignoreAt) {
1323
+ let big = doc.length >= 1000 /* MinCacheLen */;
1324
+ let cached = big && cache.get(doc);
1325
+ if (cached)
1326
+ return cached;
1327
+ let result = [], seen = Object.create(null);
1328
+ if (doc.children) {
1329
+ let pos = 0;
1330
+ for (let ch of doc.children) {
1331
+ if (ch.length >= 1000 /* MinCacheLen */) {
1332
+ for (let c of collectWords(ch, cache, wordRE, to - pos, ignoreAt - pos)) {
1333
+ if (!seen[c.label]) {
1334
+ seen[c.label] = true;
1335
+ result.push(c);
1267
1336
  }
1268
1337
  }
1269
- start = -1;
1270
1338
  }
1271
- if (i == value.length)
1272
- break;
1339
+ else {
1340
+ storeWords(ch, wordRE, result, seen, ignoreAt - pos);
1341
+ }
1342
+ pos += ch.length + 1;
1273
1343
  }
1274
- pos += value.length;
1275
1344
  }
1276
- return { from, options, span: /^\w*/ };
1345
+ else {
1346
+ storeWords(doc, wordRE, result, seen, ignoreAt);
1347
+ }
1348
+ if (big && result.length < 2000 /* MaxList */)
1349
+ cache.set(doc, result);
1350
+ return result;
1351
+ }
1352
+ /**
1353
+ A completion source that will scan the document for words (using a
1354
+ [character categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer)), and
1355
+ return those as completions.
1356
+ */
1357
+ const completeAnyWord = context => {
1358
+ let wordChars = context.state.languageDataAt("wordChars", context.pos).join("");
1359
+ let re = wordRE(wordChars);
1360
+ let token = context.matchBefore(mapRE(re, s => s + "$"));
1361
+ if (!token && !context.explicit)
1362
+ return null;
1363
+ let from = token ? token.from : context.pos;
1364
+ let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
1365
+ return { from, options, span: mapRE(re, s => "^" + s) };
1277
1366
  };
1278
1367
 
1279
1368
  /**
@@ -1308,7 +1397,7 @@ const completionKeymap = [
1308
1397
  { key: "PageUp", run: moveCompletionSelection(false, "page") },
1309
1398
  { key: "Enter", run: acceptCompletion }
1310
1399
  ];
1311
- const completionKeymapExt = state.Prec.override(view.keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1400
+ const completionKeymapExt = state.Prec.highest(view.keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1312
1401
  /**
1313
1402
  Get the current completion status. When completions are available,
1314
1403
  this will return `"active"`. When completions are pending (in the
@@ -1328,6 +1417,14 @@ function currentCompletions(state) {
1328
1417
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1329
1418
  return open ? open.options.map(o => o.completion) : [];
1330
1419
  }
1420
+ /**
1421
+ Return the currently selected completion, if any.
1422
+ */
1423
+ function selectedCompletion(state) {
1424
+ var _a;
1425
+ let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1426
+ return open ? open.options[open.selected].completion : null;
1427
+ }
1331
1428
 
1332
1429
  exports.CompletionContext = CompletionContext;
1333
1430
  exports.acceptCompletion = acceptCompletion;
@@ -1343,7 +1440,9 @@ exports.ifIn = ifIn;
1343
1440
  exports.ifNotIn = ifNotIn;
1344
1441
  exports.moveCompletionSelection = moveCompletionSelection;
1345
1442
  exports.nextSnippetField = nextSnippetField;
1443
+ exports.pickedCompletion = pickedCompletion;
1346
1444
  exports.prevSnippetField = prevSnippetField;
1445
+ exports.selectedCompletion = selectedCompletion;
1347
1446
  exports.snippet = snippet;
1348
1447
  exports.snippetCompletion = snippetCompletion;
1349
1448
  exports.snippetKeymap = snippetKeymap;