@codemirror/autocomplete 0.19.4 → 0.19.8

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,37 @@
1
+ ## 0.19.8 (2021-11-17)
2
+
3
+ ### Bug fixes
4
+
5
+ Give the completion tooltip a minimal width, and show ellipsis when completions overflow the tooltip width.
6
+
7
+ ### New features
8
+
9
+ `autocompletion` now accepts an `aboveCursor` option to make the completion tooltip show up above the cursor.
10
+
11
+ ## 0.19.7 (2021-11-16)
12
+
13
+ ### Bug fixes
14
+
15
+ Make option deduplication less aggressive, so that options with different `type` or `apply` fields don't get merged.
16
+
17
+ ## 0.19.6 (2021-11-12)
18
+
19
+ ### Bug fixes
20
+
21
+ Fix an issue where parsing a snippet with a field that was labeled only by a number crashed.
22
+
23
+ ## 0.19.5 (2021-11-09)
24
+
25
+ ### Bug fixes
26
+
27
+ Make sure info tooltips don't stick out of the bottom of the page.
28
+
29
+ ### New features
30
+
31
+ The package exports a new function `selectedCompletion`, which can be used to find out which completion is currently selected.
32
+
33
+ Transactions created by picking a completion now have an annotation (`pickedCompletion`) holding the original completion.
34
+
1
35
  ## 0.19.4 (2021-10-24)
2
36
 
3
37
  ### 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 {
@@ -303,6 +309,7 @@ const completionConfig = state.Facet.define({
303
309
  maxRenderedOptions: 100,
304
310
  defaultKeymap: true,
305
311
  optionClass: () => "",
312
+ aboveCursor: false,
306
313
  icons: true,
307
314
  addToOptions: []
308
315
  }, {
@@ -317,107 +324,6 @@ function joinClass(a, b) {
317
324
  return a ? b ? a + " " + b : a : b;
318
325
  }
319
326
 
320
- const MaxInfoWidth = 300;
321
- const baseTheme = view.EditorView.baseTheme({
322
- ".cm-tooltip.cm-tooltip-autocomplete": {
323
- "& > ul": {
324
- fontFamily: "monospace",
325
- whiteSpace: "nowrap",
326
- overflow: "auto",
327
- maxWidth_fallback: "700px",
328
- maxWidth: "min(700px, 95vw)",
329
- maxHeight: "10em",
330
- listStyle: "none",
331
- margin: 0,
332
- padding: 0,
333
- "& > li": {
334
- cursor: "pointer",
335
- padding: "1px 1em 1px 3px",
336
- lineHeight: 1.2
337
- },
338
- }
339
- },
340
- "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
341
- background: "#39e",
342
- color: "white",
343
- },
344
- "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
345
- background: "#347",
346
- color: "white",
347
- },
348
- ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
349
- content: '"···"',
350
- opacity: 0.5,
351
- display: "block",
352
- textAlign: "center"
353
- },
354
- ".cm-tooltip.cm-completionInfo": {
355
- position: "absolute",
356
- padding: "3px 9px",
357
- width: "max-content",
358
- maxWidth: MaxInfoWidth + "px",
359
- },
360
- ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
361
- ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
362
- "&light .cm-snippetField": { backgroundColor: "#00000022" },
363
- "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
364
- ".cm-snippetFieldPosition": {
365
- verticalAlign: "text-top",
366
- width: 0,
367
- height: "1.15em",
368
- margin: "0 -0.7px -.7em",
369
- borderLeft: "1.4px dotted #888"
370
- },
371
- ".cm-completionMatchedText": {
372
- textDecoration: "underline"
373
- },
374
- ".cm-completionDetail": {
375
- marginLeft: "0.5em",
376
- fontStyle: "italic"
377
- },
378
- ".cm-completionIcon": {
379
- fontSize: "90%",
380
- width: ".8em",
381
- display: "inline-block",
382
- textAlign: "center",
383
- paddingRight: ".6em",
384
- opacity: "0.6"
385
- },
386
- ".cm-completionIcon-function, .cm-completionIcon-method": {
387
- "&:after": { content: "'ƒ'" }
388
- },
389
- ".cm-completionIcon-class": {
390
- "&:after": { content: "'○'" }
391
- },
392
- ".cm-completionIcon-interface": {
393
- "&:after": { content: "'◌'" }
394
- },
395
- ".cm-completionIcon-variable": {
396
- "&:after": { content: "'𝑥'" }
397
- },
398
- ".cm-completionIcon-constant": {
399
- "&:after": { content: "'𝐶'" }
400
- },
401
- ".cm-completionIcon-type": {
402
- "&:after": { content: "'𝑡'" }
403
- },
404
- ".cm-completionIcon-enum": {
405
- "&:after": { content: "'∪'" }
406
- },
407
- ".cm-completionIcon-property": {
408
- "&:after": { content: "'□'" }
409
- },
410
- ".cm-completionIcon-keyword": {
411
- "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
412
- },
413
- ".cm-completionIcon-namespace": {
414
- "&:after": { content: "'▢'" }
415
- },
416
- ".cm-completionIcon-text": {
417
- "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
418
- }
419
- });
420
-
421
327
  function optionContent(config) {
422
328
  let content = config.addToOptions.slice();
423
329
  if (config.icons)
@@ -575,17 +481,17 @@ class CompletionTooltip {
575
481
  }
576
482
  measureInfo() {
577
483
  let sel = this.dom.querySelector("[aria-selected]");
578
- if (!sel)
484
+ if (!sel || !this.info)
579
485
  return null;
580
- let rect = this.dom.getBoundingClientRect();
581
- let top = sel.getBoundingClientRect().top - rect.top;
486
+ let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
487
+ let top = Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height) - rect.top;
582
488
  if (top < 0 || top > this.list.clientHeight - 10)
583
489
  return null;
584
490
  let left = this.view.textDirection == view.Direction.RTL;
585
491
  let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
586
- if (left && spaceLeft < Math.min(MaxInfoWidth, spaceRight))
492
+ if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
587
493
  left = false;
588
- else if (!left && spaceRight < Math.min(MaxInfoWidth, spaceLeft))
494
+ else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
589
495
  left = true;
590
496
  return { top, left };
591
497
  }
@@ -665,7 +571,8 @@ function sortOptions(active, state) {
665
571
  for (let opt of options.sort(cmpOption)) {
666
572
  if (result.length == MaxOptions)
667
573
  break;
668
- if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail)
574
+ if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
575
+ prev.type != opt.completion.type || prev.apply != opt.completion.apply)
669
576
  result.push(opt);
670
577
  else if (score(opt.completion) > score(prev))
671
578
  result[result.length - 1] = opt;
@@ -685,7 +592,7 @@ class CompletionDialog {
685
592
  return selected == this.selected || selected >= this.options.length ? this
686
593
  : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
687
594
  }
688
- static build(active, state, id, prev) {
595
+ static build(active, state, id, prev, conf) {
689
596
  let options = sortOptions(active, state);
690
597
  if (!options.length)
691
598
  return null;
@@ -699,7 +606,8 @@ class CompletionDialog {
699
606
  }
700
607
  return new CompletionDialog(options, makeAttrs(id, selected), {
701
608
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
702
- create: completionTooltip(completionState)
609
+ create: completionTooltip(completionState),
610
+ above: conf.aboveCursor,
703
611
  }, prev ? prev.timestamp : Date.now(), selected);
704
612
  }
705
613
  map(changes) {
@@ -727,7 +635,7 @@ class CompletionState {
727
635
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
728
636
  active = this.active;
729
637
  let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
730
- !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open)
638
+ !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
731
639
  : this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
732
640
  if (!open && active.every(a => a.state != 1 /* Pending */) && active.some(a => a.hasResult()))
733
641
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* Inactive */) : a);
@@ -1058,6 +966,109 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
1058
966
  }
1059
967
  });
1060
968
 
969
+ const baseTheme = view.EditorView.baseTheme({
970
+ ".cm-tooltip.cm-tooltip-autocomplete": {
971
+ "& > ul": {
972
+ fontFamily: "monospace",
973
+ whiteSpace: "nowrap",
974
+ overflow: "hidden auto",
975
+ maxWidth_fallback: "700px",
976
+ maxWidth: "min(700px, 95vw)",
977
+ minWidth: "250px",
978
+ maxHeight: "10em",
979
+ listStyle: "none",
980
+ margin: 0,
981
+ padding: 0,
982
+ "& > li": {
983
+ overflowX: "hidden",
984
+ textOverflow: "ellipsis",
985
+ cursor: "pointer",
986
+ padding: "1px 3px",
987
+ lineHeight: 1.2
988
+ },
989
+ }
990
+ },
991
+ "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
992
+ background: "#39e",
993
+ color: "white",
994
+ },
995
+ "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
996
+ background: "#347",
997
+ color: "white",
998
+ },
999
+ ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
1000
+ content: '"···"',
1001
+ opacity: 0.5,
1002
+ display: "block",
1003
+ textAlign: "center"
1004
+ },
1005
+ ".cm-tooltip.cm-completionInfo": {
1006
+ position: "absolute",
1007
+ padding: "3px 9px",
1008
+ width: "max-content",
1009
+ maxWidth: "300px",
1010
+ },
1011
+ ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
1012
+ ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
1013
+ "&light .cm-snippetField": { backgroundColor: "#00000022" },
1014
+ "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
1015
+ ".cm-snippetFieldPosition": {
1016
+ verticalAlign: "text-top",
1017
+ width: 0,
1018
+ height: "1.15em",
1019
+ margin: "0 -0.7px -.7em",
1020
+ borderLeft: "1.4px dotted #888"
1021
+ },
1022
+ ".cm-completionMatchedText": {
1023
+ textDecoration: "underline"
1024
+ },
1025
+ ".cm-completionDetail": {
1026
+ marginLeft: "0.5em",
1027
+ fontStyle: "italic"
1028
+ },
1029
+ ".cm-completionIcon": {
1030
+ fontSize: "90%",
1031
+ width: ".8em",
1032
+ display: "inline-block",
1033
+ textAlign: "center",
1034
+ paddingRight: ".6em",
1035
+ opacity: "0.6"
1036
+ },
1037
+ ".cm-completionIcon-function, .cm-completionIcon-method": {
1038
+ "&:after": { content: "'ƒ'" }
1039
+ },
1040
+ ".cm-completionIcon-class": {
1041
+ "&:after": { content: "'○'" }
1042
+ },
1043
+ ".cm-completionIcon-interface": {
1044
+ "&:after": { content: "'◌'" }
1045
+ },
1046
+ ".cm-completionIcon-variable": {
1047
+ "&:after": { content: "'𝑥'" }
1048
+ },
1049
+ ".cm-completionIcon-constant": {
1050
+ "&:after": { content: "'𝐶'" }
1051
+ },
1052
+ ".cm-completionIcon-type": {
1053
+ "&:after": { content: "'𝑡'" }
1054
+ },
1055
+ ".cm-completionIcon-enum": {
1056
+ "&:after": { content: "'∪'" }
1057
+ },
1058
+ ".cm-completionIcon-property": {
1059
+ "&:after": { content: "'□'" }
1060
+ },
1061
+ ".cm-completionIcon-keyword": {
1062
+ "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
1063
+ },
1064
+ ".cm-completionIcon-namespace": {
1065
+ "&:after": { content: "'▢'" }
1066
+ },
1067
+ ".cm-completionIcon-text": {
1068
+ "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
1069
+ }
1070
+ });
1071
+
1061
1072
  class FieldPos {
1062
1073
  constructor(field, line, from, to) {
1063
1074
  this.field = field;
@@ -1103,7 +1114,7 @@ class Snippet {
1103
1114
  let lines = [], positions = [], m;
1104
1115
  for (let line of template.split(/\r\n?|\n/)) {
1105
1116
  while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|([^}]*))\}/.exec(line)) {
1106
- let seq = m[1] ? +m[1] : null, name = m[2] || m[3], found = -1;
1117
+ let seq = m[1] ? +m[1] : null, name = m[2] || m[3] || "", found = -1;
1107
1118
  for (let i = 0; i < fields.length; i++) {
1108
1119
  if (seq != null ? fields[i].seq == seq : name ? fields[i].name == name : false)
1109
1120
  found = i;
@@ -1112,7 +1123,7 @@ class Snippet {
1112
1123
  let i = 0;
1113
1124
  while (i < fields.length && (seq == null || (fields[i].seq != null && fields[i].seq < seq)))
1114
1125
  i++;
1115
- fields.splice(i, 0, { seq, name: name || null });
1126
+ fields.splice(i, 0, { seq, name });
1116
1127
  found = i;
1117
1128
  for (let pos of positions)
1118
1129
  if (pos.field >= found)
@@ -1208,8 +1219,7 @@ function snippet(template) {
1208
1219
  let active = new ActiveSnippet(ranges, 0);
1209
1220
  let effects = spec.effects = [setActive.of(active)];
1210
1221
  if (editor.state.field(snippetState, false) === undefined)
1211
- effects.push(state.StateEffect.appendConfig.of([snippetState.init(() => active), addSnippetKeymap,
1212
- snippetPointerHandler, baseTheme]));
1222
+ effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
1213
1223
  }
1214
1224
  editor.dispatch(editor.state.update(spec));
1215
1225
  };
@@ -1259,7 +1269,7 @@ to [`clearSnippet`](https://codemirror.net/6/docs/ref/#autocomplete.clearSnippet
1259
1269
  const snippetKeymap = state.Facet.define({
1260
1270
  combine(maps) { return maps.length ? maps[0] : defaultSnippetKeymap; }
1261
1271
  });
1262
- const addSnippetKeymap = state.Prec.override(view.keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1272
+ const addSnippetKeymap = state.Prec.highest(view.keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1263
1273
  /**
1264
1274
  Create a completion from a snippet. Returns an object with the
1265
1275
  properties from `completion`, plus an `apply` function that
@@ -1393,7 +1403,7 @@ const completionKeymap = [
1393
1403
  { key: "PageUp", run: moveCompletionSelection(false, "page") },
1394
1404
  { key: "Enter", run: acceptCompletion }
1395
1405
  ];
1396
- const completionKeymapExt = state.Prec.override(view.keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1406
+ const completionKeymapExt = state.Prec.highest(view.keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1397
1407
  /**
1398
1408
  Get the current completion status. When completions are available,
1399
1409
  this will return `"active"`. When completions are pending (in the
@@ -1413,6 +1423,14 @@ function currentCompletions(state) {
1413
1423
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1414
1424
  return open ? open.options.map(o => o.completion) : [];
1415
1425
  }
1426
+ /**
1427
+ Return the currently selected completion, if any.
1428
+ */
1429
+ function selectedCompletion(state) {
1430
+ var _a;
1431
+ let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1432
+ return open ? open.options[open.selected].completion : null;
1433
+ }
1416
1434
 
1417
1435
  exports.CompletionContext = CompletionContext;
1418
1436
  exports.acceptCompletion = acceptCompletion;
@@ -1428,7 +1446,9 @@ exports.ifIn = ifIn;
1428
1446
  exports.ifNotIn = ifNotIn;
1429
1447
  exports.moveCompletionSelection = moveCompletionSelection;
1430
1448
  exports.nextSnippetField = nextSnippetField;
1449
+ exports.pickedCompletion = pickedCompletion;
1431
1450
  exports.prevSnippetField = prevSnippetField;
1451
+ exports.selectedCompletion = selectedCompletion;
1432
1452
  exports.snippet = snippet;
1433
1453
  exports.snippetCompletion = snippetCompletion;
1434
1454
  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';
@@ -29,6 +30,12 @@ interface CompletionConfig {
29
30
  */
30
31
  defaultKeymap?: boolean;
31
32
  /**
33
+ By default, completions are shown below the cursor when there is
34
+ space. Setting this to true will make the extension put the
35
+ completions above the cursor when possible.
36
+ */
37
+ aboveCursor?: boolean;
38
+ /**
32
39
  This can be used to add additional CSS classes to completion
33
40
  options.
34
41
  */
@@ -80,7 +87,9 @@ interface Completion {
80
87
  its [label](https://codemirror.net/6/docs/ref/#autocomplete.Completion.label). When this holds a
81
88
  string, the completion range is replaced by that string. When it
82
89
  is a function, that function is called to perform the
83
- completion.
90
+ completion. If it fires a transaction, it is responsible for
91
+ adding the [`pickedCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.pickedCompletion)
92
+ annotation to it.
84
93
  */
85
94
  apply?: string | ((view: EditorView, completion: Completion, from: number, to: number) => void);
86
95
  /**
@@ -235,6 +244,11 @@ interface CompletionResult {
235
244
  */
236
245
  filter?: boolean;
237
246
  }
247
+ /**
248
+ This annotation is added to transactions that are produced by
249
+ picking a completion.
250
+ */
251
+ declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
238
252
 
239
253
  /**
240
254
  Convert a snippet template to a function that can apply it.
@@ -344,5 +358,9 @@ declare function completionStatus(state: EditorState): null | "active" | "pendin
344
358
  Returns the available completions as an array.
345
359
  */
346
360
  declare function currentCompletions(state: EditorState): readonly Completion[];
361
+ /**
362
+ Return the currently selected completion, if any.
363
+ */
364
+ declare function selectedCompletion(state: EditorState): Completion | null;
347
365
 
348
- export { Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, prevSnippetField, snippet, snippetCompletion, snippetKeymap, startCompletion };
366
+ 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, Prec, EditorSelection, Text } 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 {
@@ -299,6 +305,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
299
305
  maxRenderedOptions: 100,
300
306
  defaultKeymap: true,
301
307
  optionClass: () => "",
308
+ aboveCursor: false,
302
309
  icons: true,
303
310
  addToOptions: []
304
311
  }, {
@@ -313,107 +320,6 @@ function joinClass(a, b) {
313
320
  return a ? b ? a + " " + b : a : b;
314
321
  }
315
322
 
316
- const MaxInfoWidth = 300;
317
- const baseTheme = /*@__PURE__*/EditorView.baseTheme({
318
- ".cm-tooltip.cm-tooltip-autocomplete": {
319
- "& > ul": {
320
- fontFamily: "monospace",
321
- whiteSpace: "nowrap",
322
- overflow: "auto",
323
- maxWidth_fallback: "700px",
324
- maxWidth: "min(700px, 95vw)",
325
- maxHeight: "10em",
326
- listStyle: "none",
327
- margin: 0,
328
- padding: 0,
329
- "& > li": {
330
- cursor: "pointer",
331
- padding: "1px 1em 1px 3px",
332
- lineHeight: 1.2
333
- },
334
- }
335
- },
336
- "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
337
- background: "#39e",
338
- color: "white",
339
- },
340
- "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
341
- background: "#347",
342
- color: "white",
343
- },
344
- ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
345
- content: '"···"',
346
- opacity: 0.5,
347
- display: "block",
348
- textAlign: "center"
349
- },
350
- ".cm-tooltip.cm-completionInfo": {
351
- position: "absolute",
352
- padding: "3px 9px",
353
- width: "max-content",
354
- maxWidth: MaxInfoWidth + "px",
355
- },
356
- ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
357
- ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
358
- "&light .cm-snippetField": { backgroundColor: "#00000022" },
359
- "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
360
- ".cm-snippetFieldPosition": {
361
- verticalAlign: "text-top",
362
- width: 0,
363
- height: "1.15em",
364
- margin: "0 -0.7px -.7em",
365
- borderLeft: "1.4px dotted #888"
366
- },
367
- ".cm-completionMatchedText": {
368
- textDecoration: "underline"
369
- },
370
- ".cm-completionDetail": {
371
- marginLeft: "0.5em",
372
- fontStyle: "italic"
373
- },
374
- ".cm-completionIcon": {
375
- fontSize: "90%",
376
- width: ".8em",
377
- display: "inline-block",
378
- textAlign: "center",
379
- paddingRight: ".6em",
380
- opacity: "0.6"
381
- },
382
- ".cm-completionIcon-function, .cm-completionIcon-method": {
383
- "&:after": { content: "'ƒ'" }
384
- },
385
- ".cm-completionIcon-class": {
386
- "&:after": { content: "'○'" }
387
- },
388
- ".cm-completionIcon-interface": {
389
- "&:after": { content: "'◌'" }
390
- },
391
- ".cm-completionIcon-variable": {
392
- "&:after": { content: "'𝑥'" }
393
- },
394
- ".cm-completionIcon-constant": {
395
- "&:after": { content: "'𝐶'" }
396
- },
397
- ".cm-completionIcon-type": {
398
- "&:after": { content: "'𝑡'" }
399
- },
400
- ".cm-completionIcon-enum": {
401
- "&:after": { content: "'∪'" }
402
- },
403
- ".cm-completionIcon-property": {
404
- "&:after": { content: "'□'" }
405
- },
406
- ".cm-completionIcon-keyword": {
407
- "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
408
- },
409
- ".cm-completionIcon-namespace": {
410
- "&:after": { content: "'▢'" }
411
- },
412
- ".cm-completionIcon-text": {
413
- "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
414
- }
415
- });
416
-
417
323
  function optionContent(config) {
418
324
  let content = config.addToOptions.slice();
419
325
  if (config.icons)
@@ -571,17 +477,17 @@ class CompletionTooltip {
571
477
  }
572
478
  measureInfo() {
573
479
  let sel = this.dom.querySelector("[aria-selected]");
574
- if (!sel)
480
+ if (!sel || !this.info)
575
481
  return null;
576
- let rect = this.dom.getBoundingClientRect();
577
- let top = sel.getBoundingClientRect().top - rect.top;
482
+ let rect = this.dom.getBoundingClientRect(), infoRect = this.info.getBoundingClientRect();
483
+ let top = Math.min(sel.getBoundingClientRect().top, innerHeight - infoRect.height) - rect.top;
578
484
  if (top < 0 || top > this.list.clientHeight - 10)
579
485
  return null;
580
486
  let left = this.view.textDirection == Direction.RTL;
581
487
  let spaceLeft = rect.left, spaceRight = innerWidth - rect.right;
582
- if (left && spaceLeft < Math.min(MaxInfoWidth, spaceRight))
488
+ if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
583
489
  left = false;
584
- else if (!left && spaceRight < Math.min(MaxInfoWidth, spaceLeft))
490
+ else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
585
491
  left = true;
586
492
  return { top, left };
587
493
  }
@@ -661,7 +567,8 @@ function sortOptions(active, state) {
661
567
  for (let opt of options.sort(cmpOption)) {
662
568
  if (result.length == MaxOptions)
663
569
  break;
664
- if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail)
570
+ if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
571
+ prev.type != opt.completion.type || prev.apply != opt.completion.apply)
665
572
  result.push(opt);
666
573
  else if (score(opt.completion) > score(prev))
667
574
  result[result.length - 1] = opt;
@@ -681,7 +588,7 @@ class CompletionDialog {
681
588
  return selected == this.selected || selected >= this.options.length ? this
682
589
  : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
683
590
  }
684
- static build(active, state, id, prev) {
591
+ static build(active, state, id, prev, conf) {
685
592
  let options = sortOptions(active, state);
686
593
  if (!options.length)
687
594
  return null;
@@ -695,7 +602,8 @@ class CompletionDialog {
695
602
  }
696
603
  return new CompletionDialog(options, makeAttrs(id, selected), {
697
604
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
698
- create: completionTooltip(completionState)
605
+ create: completionTooltip(completionState),
606
+ above: conf.aboveCursor,
699
607
  }, prev ? prev.timestamp : Date.now(), selected);
700
608
  }
701
609
  map(changes) {
@@ -723,7 +631,7 @@ class CompletionState {
723
631
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
724
632
  active = this.active;
725
633
  let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
726
- !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open)
634
+ !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
727
635
  : this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
728
636
  if (!open && active.every(a => a.state != 1 /* Pending */) && active.some(a => a.hasResult()))
729
637
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* Inactive */) : a);
@@ -1054,6 +962,109 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1054
962
  }
1055
963
  });
1056
964
 
965
+ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
966
+ ".cm-tooltip.cm-tooltip-autocomplete": {
967
+ "& > ul": {
968
+ fontFamily: "monospace",
969
+ whiteSpace: "nowrap",
970
+ overflow: "hidden auto",
971
+ maxWidth_fallback: "700px",
972
+ maxWidth: "min(700px, 95vw)",
973
+ minWidth: "250px",
974
+ maxHeight: "10em",
975
+ listStyle: "none",
976
+ margin: 0,
977
+ padding: 0,
978
+ "& > li": {
979
+ overflowX: "hidden",
980
+ textOverflow: "ellipsis",
981
+ cursor: "pointer",
982
+ padding: "1px 3px",
983
+ lineHeight: 1.2
984
+ },
985
+ }
986
+ },
987
+ "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
988
+ background: "#39e",
989
+ color: "white",
990
+ },
991
+ "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
992
+ background: "#347",
993
+ color: "white",
994
+ },
995
+ ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
996
+ content: '"···"',
997
+ opacity: 0.5,
998
+ display: "block",
999
+ textAlign: "center"
1000
+ },
1001
+ ".cm-tooltip.cm-completionInfo": {
1002
+ position: "absolute",
1003
+ padding: "3px 9px",
1004
+ width: "max-content",
1005
+ maxWidth: "300px",
1006
+ },
1007
+ ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
1008
+ ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
1009
+ "&light .cm-snippetField": { backgroundColor: "#00000022" },
1010
+ "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
1011
+ ".cm-snippetFieldPosition": {
1012
+ verticalAlign: "text-top",
1013
+ width: 0,
1014
+ height: "1.15em",
1015
+ margin: "0 -0.7px -.7em",
1016
+ borderLeft: "1.4px dotted #888"
1017
+ },
1018
+ ".cm-completionMatchedText": {
1019
+ textDecoration: "underline"
1020
+ },
1021
+ ".cm-completionDetail": {
1022
+ marginLeft: "0.5em",
1023
+ fontStyle: "italic"
1024
+ },
1025
+ ".cm-completionIcon": {
1026
+ fontSize: "90%",
1027
+ width: ".8em",
1028
+ display: "inline-block",
1029
+ textAlign: "center",
1030
+ paddingRight: ".6em",
1031
+ opacity: "0.6"
1032
+ },
1033
+ ".cm-completionIcon-function, .cm-completionIcon-method": {
1034
+ "&:after": { content: "'ƒ'" }
1035
+ },
1036
+ ".cm-completionIcon-class": {
1037
+ "&:after": { content: "'○'" }
1038
+ },
1039
+ ".cm-completionIcon-interface": {
1040
+ "&:after": { content: "'◌'" }
1041
+ },
1042
+ ".cm-completionIcon-variable": {
1043
+ "&:after": { content: "'𝑥'" }
1044
+ },
1045
+ ".cm-completionIcon-constant": {
1046
+ "&:after": { content: "'𝐶'" }
1047
+ },
1048
+ ".cm-completionIcon-type": {
1049
+ "&:after": { content: "'𝑡'" }
1050
+ },
1051
+ ".cm-completionIcon-enum": {
1052
+ "&:after": { content: "'∪'" }
1053
+ },
1054
+ ".cm-completionIcon-property": {
1055
+ "&:after": { content: "'□'" }
1056
+ },
1057
+ ".cm-completionIcon-keyword": {
1058
+ "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
1059
+ },
1060
+ ".cm-completionIcon-namespace": {
1061
+ "&:after": { content: "'▢'" }
1062
+ },
1063
+ ".cm-completionIcon-text": {
1064
+ "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
1065
+ }
1066
+ });
1067
+
1057
1068
  class FieldPos {
1058
1069
  constructor(field, line, from, to) {
1059
1070
  this.field = field;
@@ -1099,7 +1110,7 @@ class Snippet {
1099
1110
  let lines = [], positions = [], m;
1100
1111
  for (let line of template.split(/\r\n?|\n/)) {
1101
1112
  while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|([^}]*))\}/.exec(line)) {
1102
- let seq = m[1] ? +m[1] : null, name = m[2] || m[3], found = -1;
1113
+ let seq = m[1] ? +m[1] : null, name = m[2] || m[3] || "", found = -1;
1103
1114
  for (let i = 0; i < fields.length; i++) {
1104
1115
  if (seq != null ? fields[i].seq == seq : name ? fields[i].name == name : false)
1105
1116
  found = i;
@@ -1108,7 +1119,7 @@ class Snippet {
1108
1119
  let i = 0;
1109
1120
  while (i < fields.length && (seq == null || (fields[i].seq != null && fields[i].seq < seq)))
1110
1121
  i++;
1111
- fields.splice(i, 0, { seq, name: name || null });
1122
+ fields.splice(i, 0, { seq, name });
1112
1123
  found = i;
1113
1124
  for (let pos of positions)
1114
1125
  if (pos.field >= found)
@@ -1204,8 +1215,7 @@ function snippet(template) {
1204
1215
  let active = new ActiveSnippet(ranges, 0);
1205
1216
  let effects = spec.effects = [setActive.of(active)];
1206
1217
  if (editor.state.field(snippetState, false) === undefined)
1207
- effects.push(StateEffect.appendConfig.of([snippetState.init(() => active), addSnippetKeymap,
1208
- snippetPointerHandler, baseTheme]));
1218
+ effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
1209
1219
  }
1210
1220
  editor.dispatch(editor.state.update(spec));
1211
1221
  };
@@ -1255,7 +1265,7 @@ to [`clearSnippet`](https://codemirror.net/6/docs/ref/#autocomplete.clearSnippet
1255
1265
  const snippetKeymap = /*@__PURE__*/Facet.define({
1256
1266
  combine(maps) { return maps.length ? maps[0] : defaultSnippetKeymap; }
1257
1267
  });
1258
- const addSnippetKeymap = /*@__PURE__*/Prec.override(/*@__PURE__*/keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1268
+ const addSnippetKeymap = /*@__PURE__*/Prec.highest(/*@__PURE__*/keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1259
1269
  /**
1260
1270
  Create a completion from a snippet. Returns an object with the
1261
1271
  properties from `completion`, plus an `apply` function that
@@ -1389,7 +1399,7 @@ const completionKeymap = [
1389
1399
  { key: "PageUp", run: /*@__PURE__*/moveCompletionSelection(false, "page") },
1390
1400
  { key: "Enter", run: acceptCompletion }
1391
1401
  ];
1392
- const completionKeymapExt = /*@__PURE__*/Prec.override(/*@__PURE__*/keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1402
+ const completionKeymapExt = /*@__PURE__*/Prec.highest(/*@__PURE__*/keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
1393
1403
  /**
1394
1404
  Get the current completion status. When completions are available,
1395
1405
  this will return `"active"`. When completions are pending (in the
@@ -1409,5 +1419,13 @@ function currentCompletions(state) {
1409
1419
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1410
1420
  return open ? open.options.map(o => o.completion) : [];
1411
1421
  }
1422
+ /**
1423
+ Return the currently selected completion, if any.
1424
+ */
1425
+ function selectedCompletion(state) {
1426
+ var _a;
1427
+ let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1428
+ return open ? open.options[open.selected].completion : null;
1429
+ }
1412
1430
 
1413
- export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, ifIn, ifNotIn, moveCompletionSelection, nextSnippetField, prevSnippetField, snippet, snippetCompletion, snippetKeymap, startCompletion };
1431
+ 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.4",
3
+ "version": "0.19.8",
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.2",
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",