@codemirror/autocomplete 0.19.2 → 0.19.6

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.6 (2021-11-12)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where parsing a snippet with a field that was labeled only by a number crashed.
6
+
7
+ ## 0.19.5 (2021-11-09)
8
+
9
+ ### Bug fixes
10
+
11
+ Make sure info tooltips don't stick out of the bottom of the page.
12
+
13
+ ### New features
14
+
15
+ The package exports a new function `selectedCompletion`, which can be used to find out which completion is currently selected.
16
+
17
+ Transactions created by picking a completion now have an annotation (`pickedCompletion`) holding the original completion.
18
+
19
+ ## 0.19.4 (2021-10-24)
20
+
21
+ ### Bug fixes
22
+
23
+ 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.
24
+
25
+ Fix incorrect match underline for some kinds of matched completions.
26
+
27
+ ## 0.19.3 (2021-08-31)
28
+
29
+ ### Bug fixes
30
+
31
+ Improve the sorting of completions by using `localeCompare`.
32
+
33
+ Fix reading of autocompletions in NVDA screen reader.
34
+
35
+ ### New features
36
+
37
+ The new `icons` option can be used to turn off icons in the completion list.
38
+
39
+ The `optionClass` option can now be used to add CSS classes to the options in the completion list.
40
+
41
+ It is now possible to inject additional content into rendered completion options with the `addToOptions` configuration option.
42
+
1
43
  ## 0.19.2 (2021-08-25)
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;
@@ -1057,7 +1108,7 @@ class Snippet {
1057
1108
  let lines = [], positions = [], m;
1058
1109
  for (let line of template.split(/\r\n?|\n/)) {
1059
1110
  while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|([^}]*))\}/.exec(line)) {
1060
- let seq = m[1] ? +m[1] : null, name = m[2] || m[3], found = -1;
1111
+ let seq = m[1] ? +m[1] : null, name = m[2] || m[3] || "", found = -1;
1061
1112
  for (let i = 0; i < fields.length; i++) {
1062
1113
  if (seq != null ? fields[i].seq == seq : name ? fields[i].name == name : false)
1063
1114
  found = i;
@@ -1066,7 +1117,7 @@ class Snippet {
1066
1117
  let i = 0;
1067
1118
  while (i < fields.length && (seq == null || (fields[i].seq != null && fields[i].seq < seq)))
1068
1119
  i++;
1069
- fields.splice(i, 0, { seq, name: name || null });
1120
+ fields.splice(i, 0, { seq, name });
1070
1121
  found = i;
1071
1122
  for (let pos of positions)
1072
1123
  if (pos.field >= found)
@@ -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
@@ -1347,7 +1397,7 @@ const completionKeymap = [
1347
1397
  { key: "PageUp", run: moveCompletionSelection(false, "page") },
1348
1398
  { key: "Enter", run: acceptCompletion }
1349
1399
  ];
1350
- 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] : []));
1351
1401
  /**
1352
1402
  Get the current completion status. When completions are available,
1353
1403
  this will return `"active"`. When completions are pending (in the
@@ -1367,6 +1417,14 @@ function currentCompletions(state) {
1367
1417
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1368
1418
  return open ? open.options.map(o => o.completion) : [];
1369
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
+ }
1370
1428
 
1371
1429
  exports.CompletionContext = CompletionContext;
1372
1430
  exports.acceptCompletion = acceptCompletion;
@@ -1382,7 +1440,9 @@ exports.ifIn = ifIn;
1382
1440
  exports.ifNotIn = ifNotIn;
1383
1441
  exports.moveCompletionSelection = moveCompletionSelection;
1384
1442
  exports.nextSnippetField = nextSnippetField;
1443
+ exports.pickedCompletion = pickedCompletion;
1385
1444
  exports.prevSnippetField = prevSnippetField;
1445
+ exports.selectedCompletion = selectedCompletion;
1386
1446
  exports.snippet = snippet;
1387
1447
  exports.snippetCompletion = snippetCompletion;
1388
1448
  exports.snippetKeymap = snippetKeymap;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as _codemirror_state from '@codemirror/state';
1
2
  import { EditorState, Transaction, StateCommand, Facet, Extension } from '@codemirror/state';
2
3
  import { EditorView, KeyBinding, Command } from '@codemirror/view';
3
4
  import * as _lezer_common from '@lezer/common';
@@ -12,7 +13,8 @@ interface CompletionConfig {
12
13
  Override the completion sources used. By default, they will be
13
14
  taken from the `"autocomplete"` [language
14
15
  data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) (which should hold
15
- [completion sources](https://codemirror.net/6/docs/ref/#autocomplete.CompletionSource)).
16
+ [completion sources](https://codemirror.net/6/docs/ref/#autocomplete.CompletionSource) or arrays
17
+ of [completions](https://codemirror.net/6/docs/ref/#autocomplete.Completion)).
16
18
  */
17
19
  override?: readonly CompletionSource[] | null;
18
20
  /**
@@ -27,6 +29,30 @@ interface CompletionConfig {
27
29
  same keys.)
28
30
  */
29
31
  defaultKeymap?: boolean;
32
+ /**
33
+ This can be used to add additional CSS classes to completion
34
+ options.
35
+ */
36
+ optionClass?: (completion: Completion) => string;
37
+ /**
38
+ By default, the library will render icons based on the
39
+ completion's [type](https://codemirror.net/6/docs/ref/#autocomplete.Completion.type) in front of
40
+ each option. Set this to false to turn that off.
41
+ */
42
+ icons?: boolean;
43
+ /**
44
+ This option can be used to inject additional content into
45
+ options. The `render` function will be called for each visible
46
+ completion, and should produce a DOM node to show. `position`
47
+ determines where in the DOM the result appears, relative to
48
+ other added widgets and the standard content. The default icons
49
+ have position 20, the label position 50, and the detail position
50
+ 70.
51
+ */
52
+ addToOptions?: {
53
+ render: (completion: Completion, state: EditorState) => Node | null;
54
+ position: number;
55
+ }[];
30
56
  }
31
57
 
32
58
  /**
@@ -55,7 +81,9 @@ interface Completion {
55
81
  its [label](https://codemirror.net/6/docs/ref/#autocomplete.Completion.label). When this holds a
56
82
  string, the completion range is replaced by that string. When it
57
83
  is a function, that function is called to perform the
58
- completion.
84
+ completion. If it fires a transaction, it is responsible for
85
+ adding the [`pickedCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.pickedCompletion)
86
+ annotation to it.
59
87
  */
60
88
  apply?: string | ((view: EditorView, completion: Completion, from: number, to: number) => void);
61
89
  /**
@@ -210,6 +238,11 @@ interface CompletionResult {
210
238
  */
211
239
  filter?: boolean;
212
240
  }
241
+ /**
242
+ This annotation is added to transactions that are produced by
243
+ picking a completion.
244
+ */
245
+ declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
213
246
 
214
247
  /**
215
248
  Convert a snippet template to a function that can apply it.
@@ -319,5 +352,9 @@ declare function completionStatus(state: EditorState): null | "active" | "pendin
319
352
  Returns the available completions as an array.
320
353
  */
321
354
  declare function currentCompletions(state: EditorState): readonly Completion[];
355
+ /**
356
+ Return the currently selected completion, if any.
357
+ */
358
+ declare function selectedCompletion(state: EditorState): Completion | null;
322
359
 
323
- export { Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, prevSnippetField, snippet, snippetCompletion, snippetKeymap, startCompletion };
360
+ export { Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { Facet, combineConfig, StateEffect, StateField, Text, EditorSelection, Prec } from '@codemirror/state';
2
- import { EditorView, Direction, logException, ViewPlugin, Decoration, WidgetType, keymap } from '@codemirror/view';
1
+ import { Annotation, Facet, combineConfig, StateEffect, StateField, Prec, EditorSelection, Text } from '@codemirror/state';
2
+ import { Direction, logException, EditorView, ViewPlugin, Decoration, WidgetType, keymap } from '@codemirror/view';
3
3
  import { showTooltip } from '@codemirror/tooltip';
4
4
  import { syntaxTree, indentUnit } from '@codemirror/language';
5
5
  import { codePointAt, codePointSize, fromCodePoint } from '@codemirror/text';
@@ -146,6 +146,11 @@ function ensureAnchor(expr, start) {
146
146
  return expr;
147
147
  return new RegExp(`${addStart ? "^" : ""}(?:${source})${addEnd ? "$" : ""}`, (_a = expr.flags) !== null && _a !== void 0 ? _a : (expr.ignoreCase ? "i" : ""));
148
148
  }
149
+ /**
150
+ This annotation is added to transactions that are produced by
151
+ picking a completion.
152
+ */
153
+ const pickedCompletion = /*@__PURE__*/Annotation.define();
149
154
  function applyCompletion(view, option) {
150
155
  let apply = option.completion.apply || option.completion.label;
151
156
  let result = option.source;
@@ -153,7 +158,8 @@ function applyCompletion(view, option) {
153
158
  view.dispatch({
154
159
  changes: { from: result.from, to: result.to, insert: apply },
155
160
  selection: { anchor: result.from + apply.length },
156
- userEvent: "input.complete"
161
+ userEvent: "input.complete",
162
+ annotations: pickedCompletion.of(option.completion)
157
163
  });
158
164
  }
159
165
  else {
@@ -247,7 +253,7 @@ class FuzzyMatcher {
247
253
  if (next == chars[adjacentTo] || next == folded[adjacentTo]) {
248
254
  if (adjacentTo == 0)
249
255
  adjacentStart = i;
250
- adjacentEnd = i;
256
+ adjacentEnd = i + 1;
251
257
  adjacentTo++;
252
258
  }
253
259
  else {
@@ -297,152 +303,67 @@ const completionConfig = /*@__PURE__*/Facet.define({
297
303
  activateOnTyping: true,
298
304
  override: null,
299
305
  maxRenderedOptions: 100,
300
- defaultKeymap: true
306
+ defaultKeymap: true,
307
+ optionClass: () => "",
308
+ icons: true,
309
+ addToOptions: []
301
310
  }, {
302
- defaultKeymap: (a, b) => a && b
311
+ defaultKeymap: (a, b) => a && b,
312
+ icons: (a, b) => a && b,
313
+ optionClass: (a, b) => c => joinClass(a(c), b(c)),
314
+ addToOptions: (a, b) => a.concat(b)
303
315
  });
304
316
  }
305
317
  });
318
+ function joinClass(a, b) {
319
+ return a ? b ? a + " " + b : a : b;
320
+ }
306
321
 
307
- const MaxInfoWidth = 300;
308
- const baseTheme = /*@__PURE__*/EditorView.baseTheme({
309
- ".cm-tooltip.cm-tooltip-autocomplete": {
310
- "& > ul": {
311
- fontFamily: "monospace",
312
- whiteSpace: "nowrap",
313
- overflow: "auto",
314
- maxWidth_fallback: "700px",
315
- maxWidth: "min(700px, 95vw)",
316
- maxHeight: "10em",
317
- listStyle: "none",
318
- margin: 0,
319
- padding: 0,
320
- "& > li": {
321
- cursor: "pointer",
322
- padding: "1px 1em 1px 3px",
323
- lineHeight: 1.2
322
+ function optionContent(config) {
323
+ let content = config.addToOptions.slice();
324
+ if (config.icons)
325
+ content.push({
326
+ render(completion) {
327
+ let icon = document.createElement("div");
328
+ icon.classList.add("cm-completionIcon");
329
+ if (completion.type)
330
+ icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls));
331
+ icon.setAttribute("aria-hidden", "true");
332
+ return icon;
324
333
  },
325
- "& > li[aria-selected]": {
326
- background_fallback: "#bdf",
327
- backgroundColor: "Highlight",
328
- color_fallback: "white",
329
- color: "HighlightText"
334
+ position: 20
335
+ });
336
+ content.push({
337
+ render(completion, _s, match) {
338
+ let labelElt = document.createElement("span");
339
+ labelElt.className = "cm-completionLabel";
340
+ let { label } = completion, off = 0;
341
+ for (let j = 1; j < match.length;) {
342
+ let from = match[j++], to = match[j++];
343
+ if (from > off)
344
+ labelElt.appendChild(document.createTextNode(label.slice(off, from)));
345
+ let span = labelElt.appendChild(document.createElement("span"));
346
+ span.appendChild(document.createTextNode(label.slice(from, to)));
347
+ span.className = "cm-completionMatchedText";
348
+ off = to;
330
349
  }
331
- }
332
- },
333
- ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
334
- content: '"···"',
335
- opacity: 0.5,
336
- display: "block",
337
- textAlign: "center"
338
- },
339
- ".cm-tooltip.cm-completionInfo": {
340
- position: "absolute",
341
- padding: "3px 9px",
342
- width: "max-content",
343
- maxWidth: MaxInfoWidth + "px",
344
- },
345
- ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
346
- ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
347
- "&light .cm-snippetField": { backgroundColor: "#00000022" },
348
- "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
349
- ".cm-snippetFieldPosition": {
350
- verticalAlign: "text-top",
351
- width: 0,
352
- height: "1.15em",
353
- margin: "0 -0.7px -.7em",
354
- borderLeft: "1.4px dotted #888"
355
- },
356
- ".cm-completionMatchedText": {
357
- textDecoration: "underline"
358
- },
359
- ".cm-completionDetail": {
360
- marginLeft: "0.5em",
361
- fontStyle: "italic"
362
- },
363
- ".cm-completionIcon": {
364
- fontSize: "90%",
365
- width: ".8em",
366
- display: "inline-block",
367
- textAlign: "center",
368
- paddingRight: ".6em",
369
- opacity: "0.6"
370
- },
371
- ".cm-completionIcon-function, .cm-completionIcon-method": {
372
- "&:after": { content: "'ƒ'" }
373
- },
374
- ".cm-completionIcon-class": {
375
- "&:after": { content: "'○'" }
376
- },
377
- ".cm-completionIcon-interface": {
378
- "&:after": { content: "'◌'" }
379
- },
380
- ".cm-completionIcon-variable": {
381
- "&:after": { content: "'𝑥'" }
382
- },
383
- ".cm-completionIcon-constant": {
384
- "&:after": { content: "'𝐶'" }
385
- },
386
- ".cm-completionIcon-type": {
387
- "&:after": { content: "'𝑡'" }
388
- },
389
- ".cm-completionIcon-enum": {
390
- "&:after": { content: "'∪'" }
391
- },
392
- ".cm-completionIcon-property": {
393
- "&:after": { content: "'□'" }
394
- },
395
- ".cm-completionIcon-keyword": {
396
- "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
397
- },
398
- ".cm-completionIcon-namespace": {
399
- "&:after": { content: "'▢'" }
400
- },
401
- ".cm-completionIcon-text": {
402
- "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
403
- }
404
- });
405
-
406
- function createListBox(options, id, range) {
407
- const ul = document.createElement("ul");
408
- ul.id = id;
409
- ul.setAttribute("role", "listbox");
410
- ul.setAttribute("aria-expanded", "true");
411
- for (let i = range.from; i < range.to; i++) {
412
- let { completion, match } = options[i];
413
- const li = ul.appendChild(document.createElement("li"));
414
- li.id = id + "-" + i;
415
- let icon = li.appendChild(document.createElement("div"));
416
- icon.classList.add("cm-completionIcon");
417
- if (completion.type)
418
- icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls));
419
- icon.setAttribute("aria-hidden", "true");
420
- let labelElt = li.appendChild(document.createElement("span"));
421
- labelElt.className = "cm-completionLabel";
422
- let { label, detail } = completion, off = 0;
423
- for (let j = 1; j < match.length;) {
424
- let from = match[j++], to = match[j++];
425
- if (from > off)
426
- labelElt.appendChild(document.createTextNode(label.slice(off, from)));
427
- let span = labelElt.appendChild(document.createElement("span"));
428
- span.appendChild(document.createTextNode(label.slice(from, to)));
429
- span.className = "cm-completionMatchedText";
430
- off = to;
431
- }
432
- if (off < label.length)
433
- labelElt.appendChild(document.createTextNode(label.slice(off)));
434
- if (detail) {
435
- let detailElt = li.appendChild(document.createElement("span"));
350
+ if (off < label.length)
351
+ labelElt.appendChild(document.createTextNode(label.slice(off)));
352
+ return labelElt;
353
+ },
354
+ position: 50
355
+ }, {
356
+ render(completion) {
357
+ if (!completion.detail)
358
+ return null;
359
+ let detailElt = document.createElement("span");
436
360
  detailElt.className = "cm-completionDetail";
437
- detailElt.textContent = detail;
438
- }
439
- li.setAttribute("role", "option");
440
- }
441
- if (range.from)
442
- ul.classList.add("cm-completionListIncompleteTop");
443
- if (range.to < options.length)
444
- ul.classList.add("cm-completionListIncompleteBottom");
445
- return ul;
361
+ detailElt.textContent = completion.detail;
362
+ return detailElt;
363
+ },
364
+ position: 80
365
+ });
366
+ return content.sort((a, b) => a.position - b.position).map(a => a.render);
446
367
  }
447
368
  function createInfoDialog(option, view) {
448
369
  let dom = document.createElement("div");
@@ -483,6 +404,8 @@ class CompletionTooltip {
483
404
  let cState = view.state.field(stateField);
484
405
  let { options, selected } = cState.open;
485
406
  let config = view.state.facet(completionConfig);
407
+ this.optionContent = optionContent(config);
408
+ this.optionClass = config.optionClass;
486
409
  this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
487
410
  this.dom = document.createElement("div");
488
411
  this.dom.className = "cm-tooltip-autocomplete";
@@ -495,7 +418,7 @@ class CompletionTooltip {
495
418
  }
496
419
  }
497
420
  });
498
- this.list = this.dom.appendChild(createListBox(options, cState.id, this.range));
421
+ this.list = this.dom.appendChild(this.createListBox(options, cState.id, this.range));
499
422
  this.list.addEventListener("scroll", () => {
500
423
  if (this.info)
501
424
  this.view.requestMeasure(this.placeInfo);
@@ -515,7 +438,7 @@ class CompletionTooltip {
515
438
  if (open.selected < this.range.from || open.selected >= this.range.to) {
516
439
  this.range = rangeAroundSelected(open.options.length, open.selected, this.view.state.facet(completionConfig).maxRenderedOptions);
517
440
  this.list.remove();
518
- this.list = this.dom.appendChild(createListBox(open.options, cState.id, this.range));
441
+ this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range));
519
442
  this.list.addEventListener("scroll", () => {
520
443
  if (this.info)
521
444
  this.view.requestMeasure(this.placeInfo);
@@ -553,17 +476,17 @@ class CompletionTooltip {
553
476
  }
554
477
  measureInfo() {
555
478
  let sel = this.dom.querySelector("[aria-selected]");
556
- if (!sel)
479
+ if (!sel || !this.info)
557
480
  return null;
558
- let rect = this.dom.getBoundingClientRect();
559
- let top = sel.getBoundingClientRect().top - rect.top;
481
+ let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
482
+ let top = Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height) - rect.top;
560
483
  if (top < 0 || top > this.list.clientHeight - 10)
561
484
  return null;
562
485
  let left = this.view.textDirection == Direction.RTL;
563
486
  let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
564
- if (left && spaceLeft < Math.min(MaxInfoWidth, spaceRight))
487
+ if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
565
488
  left = false;
566
- else if (!left && spaceRight < Math.min(MaxInfoWidth, spaceLeft))
489
+ else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
567
490
  left = true;
568
491
  return { top, left };
569
492
  }
@@ -574,6 +497,30 @@ class CompletionTooltip {
574
497
  this.info.classList.toggle("cm-completionInfo-right", !pos.left);
575
498
  }
576
499
  }
500
+ createListBox(options, id, range) {
501
+ const ul = document.createElement("ul");
502
+ ul.id = id;
503
+ ul.setAttribute("role", "listbox");
504
+ for (let i = range.from; i < range.to; i++) {
505
+ let { completion, match } = options[i];
506
+ const li = ul.appendChild(document.createElement("li"));
507
+ li.id = id + "-" + i;
508
+ li.setAttribute("role", "option");
509
+ let cls = this.optionClass(completion);
510
+ if (cls)
511
+ li.className = cls;
512
+ for (let source of this.optionContent) {
513
+ let node = source(completion, this.view.state, match);
514
+ if (node)
515
+ li.appendChild(node);
516
+ }
517
+ }
518
+ if (range.from)
519
+ ul.classList.add("cm-completionListIncompleteTop");
520
+ if (range.to < options.length)
521
+ ul.classList.add("cm-completionListIncompleteBottom");
522
+ return ul;
523
+ }
577
524
  }
578
525
  // We allocate a new function instance every time the completion
579
526
  // changes to force redrawing/repositioning of the tooltip
@@ -708,20 +655,24 @@ function sameResults(a, b) {
708
655
  return false;
709
656
  }
710
657
  }
658
+ const baseAttrs = {
659
+ "aria-autocomplete": "list",
660
+ "aria-expanded": "false"
661
+ };
711
662
  function makeAttrs(id, selected) {
712
663
  return {
713
664
  "aria-autocomplete": "list",
665
+ "aria-expanded": "true",
714
666
  "aria-activedescendant": id + "-" + selected,
715
- "aria-owns": id
667
+ "aria-controls": id
716
668
  };
717
669
  }
718
- const baseAttrs = { "aria-autocomplete": "list" }, none = [];
670
+ const none = [];
719
671
  function cmpOption(a, b) {
720
672
  let dScore = b.match[0] - a.match[0];
721
673
  if (dScore)
722
674
  return dScore;
723
- let lA = a.completion.label, lB = b.completion.label;
724
- return lA < lB ? -1 : lA == lB ? 0 : 1;
675
+ return a.completion.label.localeCompare(b.completion.label);
725
676
  }
726
677
  function getUserEvent(tr) {
727
678
  return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
@@ -832,7 +783,7 @@ Accept the current completion.
832
783
  */
833
784
  const acceptCompletion = (view) => {
834
785
  let cState = view.state.field(completionState, false);
835
- if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
786
+ if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
836
787
  return false;
837
788
  applyCompletion(view, cState.open.options[cState.open.selected]);
838
789
  return true;
@@ -1008,6 +959,106 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1008
959
  }
1009
960
  });
1010
961
 
962
+ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
963
+ ".cm-tooltip.cm-tooltip-autocomplete": {
964
+ "& > ul": {
965
+ fontFamily: "monospace",
966
+ whiteSpace: "nowrap",
967
+ overflow: "auto",
968
+ maxWidth_fallback: "700px",
969
+ maxWidth: "min(700px, 95vw)",
970
+ maxHeight: "10em",
971
+ listStyle: "none",
972
+ margin: 0,
973
+ padding: 0,
974
+ "& > li": {
975
+ cursor: "pointer",
976
+ padding: "1px 1em 1px 3px",
977
+ lineHeight: 1.2
978
+ },
979
+ }
980
+ },
981
+ "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
982
+ background: "#39e",
983
+ color: "white",
984
+ },
985
+ "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
986
+ background: "#347",
987
+ color: "white",
988
+ },
989
+ ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
990
+ content: '"···"',
991
+ opacity: 0.5,
992
+ display: "block",
993
+ textAlign: "center"
994
+ },
995
+ ".cm-tooltip.cm-completionInfo": {
996
+ position: "absolute",
997
+ padding: "3px 9px",
998
+ width: "max-content",
999
+ maxWidth: "300px",
1000
+ },
1001
+ ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
1002
+ ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
1003
+ "&light .cm-snippetField": { backgroundColor: "#00000022" },
1004
+ "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
1005
+ ".cm-snippetFieldPosition": {
1006
+ verticalAlign: "text-top",
1007
+ width: 0,
1008
+ height: "1.15em",
1009
+ margin: "0 -0.7px -.7em",
1010
+ borderLeft: "1.4px dotted #888"
1011
+ },
1012
+ ".cm-completionMatchedText": {
1013
+ textDecoration: "underline"
1014
+ },
1015
+ ".cm-completionDetail": {
1016
+ marginLeft: "0.5em",
1017
+ fontStyle: "italic"
1018
+ },
1019
+ ".cm-completionIcon": {
1020
+ fontSize: "90%",
1021
+ width: ".8em",
1022
+ display: "inline-block",
1023
+ textAlign: "center",
1024
+ paddingRight: ".6em",
1025
+ opacity: "0.6"
1026
+ },
1027
+ ".cm-completionIcon-function, .cm-completionIcon-method": {
1028
+ "&:after": { content: "'ƒ'" }
1029
+ },
1030
+ ".cm-completionIcon-class": {
1031
+ "&:after": { content: "'○'" }
1032
+ },
1033
+ ".cm-completionIcon-interface": {
1034
+ "&:after": { content: "'◌'" }
1035
+ },
1036
+ ".cm-completionIcon-variable": {
1037
+ "&:after": { content: "'𝑥'" }
1038
+ },
1039
+ ".cm-completionIcon-constant": {
1040
+ "&:after": { content: "'𝐶'" }
1041
+ },
1042
+ ".cm-completionIcon-type": {
1043
+ "&:after": { content: "'𝑡'" }
1044
+ },
1045
+ ".cm-completionIcon-enum": {
1046
+ "&:after": { content: "'∪'" }
1047
+ },
1048
+ ".cm-completionIcon-property": {
1049
+ "&:after": { content: "'□'" }
1050
+ },
1051
+ ".cm-completionIcon-keyword": {
1052
+ "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
1053
+ },
1054
+ ".cm-completionIcon-namespace": {
1055
+ "&:after": { content: "'▢'" }
1056
+ },
1057
+ ".cm-completionIcon-text": {
1058
+ "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
1059
+ }
1060
+ });
1061
+
1011
1062
  class FieldPos {
1012
1063
  constructor(field, line, from, to) {
1013
1064
  this.field = field;
@@ -1053,7 +1104,7 @@ class Snippet {
1053
1104
  let lines = [], positions = [], m;
1054
1105
  for (let line of template.split(/\r\n?|\n/)) {
1055
1106
  while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|([^}]*))\}/.exec(line)) {
1056
- let seq = m[1] ? +m[1] : null, name = m[2] || m[3], found = -1;
1107
+ let seq = m[1] ? +m[1] : null, name = m[2] || m[3] || "", found = -1;
1057
1108
  for (let i = 0; i < fields.length; i++) {
1058
1109
  if (seq != null ? fields[i].seq == seq : name ? fields[i].name == name : false)
1059
1110
  found = i;
@@ -1062,7 +1113,7 @@ class Snippet {
1062
1113
  let i = 0;
1063
1114
  while (i < fields.length && (seq == null || (fields[i].seq != null && fields[i].seq < seq)))
1064
1115
  i++;
1065
- fields.splice(i, 0, { seq, name: name || null });
1116
+ fields.splice(i, 0, { seq, name });
1066
1117
  found = i;
1067
1118
  for (let pos of positions)
1068
1119
  if (pos.field >= found)
@@ -1158,8 +1209,7 @@ function snippet(template) {
1158
1209
  let active = new ActiveSnippet(ranges, 0);
1159
1210
  let effects = spec.effects = [setActive.of(active)];
1160
1211
  if (editor.state.field(snippetState, false) === undefined)
1161
- effects.push(StateEffect.appendConfig.of([snippetState.init(() => active), addSnippetKeymap,
1162
- snippetPointerHandler, baseTheme]));
1212
+ effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
1163
1213
  }
1164
1214
  editor.dispatch(editor.state.update(spec));
1165
1215
  };
@@ -1209,7 +1259,7 @@ to [`clearSnippet`](https://codemirror.net/6/docs/ref/#autocomplete.clearSnippet
1209
1259
  const snippetKeymap = /*@__PURE__*/Facet.define({
1210
1260
  combine(maps) { return maps.length ? maps[0] : defaultSnippetKeymap; }
1211
1261
  });
1212
- const addSnippetKeymap = /*@__PURE__*/Prec.override(/*@__PURE__*/keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1262
+ const addSnippetKeymap = /*@__PURE__*/Prec.highest(/*@__PURE__*/keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1213
1263
  /**
1214
1264
  Create a completion from a snippet. Returns an object with the
1215
1265
  properties from `completion`, plus an `apply` function that
@@ -1343,7 +1393,7 @@ const completionKeymap = [
1343
1393
  { key: "PageUp", run: /*@__PURE__*/moveCompletionSelection(false, "page") },
1344
1394
  { key: "Enter", run: acceptCompletion }
1345
1395
  ];
1346
- const completionKeymapExt = /*@__PURE__*/Prec.override(/*@__PURE__*/keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1396
+ const completionKeymapExt = /*@__PURE__*/Prec.highest(/*@__PURE__*/keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1347
1397
  /**
1348
1398
  Get the current completion status. When completions are available,
1349
1399
  this will return `"active"`. When completions are pending (in the
@@ -1363,5 +1413,13 @@ function currentCompletions(state) {
1363
1413
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1364
1414
  return open ? open.options.map(o => o.completion) : [];
1365
1415
  }
1416
+ /**
1417
+ Return the currently selected completion, if any.
1418
+ */
1419
+ function selectedCompletion(state) {
1420
+ var _a;
1421
+ let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1422
+ return open ? open.options[open.selected].completion : null;
1423
+ }
1366
1424
 
1367
- export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, prevSnippetField, snippet, snippetCompletion, snippetKeymap, startCompletion };
1425
+ export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "0.19.2",
3
+ "version": "0.19.6",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -27,7 +27,7 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@codemirror/language": "^0.19.0",
30
- "@codemirror/state": "^0.19.0",
30
+ "@codemirror/state": "^0.19.4",
31
31
  "@codemirror/text": "^0.19.2",
32
32
  "@codemirror/tooltip": "^0.19.0",
33
33
  "@codemirror/view": "^0.19.0",