@codemirror/autocomplete 6.3.4 → 6.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 6.4.1 (2023-02-14)
2
+
3
+ ### Bug fixes
4
+
5
+ Don't consider node names in trees that aren't the same language as the one at the completion position in `ifIn` and `ifNotIn`.
6
+
7
+ Make sure completions that exactly match the input get a higher score than those that don't (so that even if the latter has a score boost, it ends up lower in the list).
8
+
9
+ ## 6.4.0 (2022-12-14)
10
+
11
+ ### Bug fixes
12
+
13
+ Fix an issue where the extension would sometimes try to draw a disabled dialog at an outdated position, leading to plugin crashes.
14
+
15
+ ### New features
16
+
17
+ A `tooltipClass` option to autocompletion can now be used to add additional CSS classes to the completion tooltip.
18
+
1
19
  ## 6.3.4 (2022-11-24)
2
20
 
3
21
  ### Bug fixes
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others
3
+ Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/index.cjs CHANGED
@@ -112,9 +112,12 @@ cursor is in a syntax node with one of the given names.
112
112
  */
113
113
  function ifIn(nodes, source) {
114
114
  return (context) => {
115
- for (let pos = language.syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent)
115
+ for (let pos = language.syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
116
116
  if (nodes.indexOf(pos.name) > -1)
117
117
  return source(context);
118
+ if (pos.type.isTop)
119
+ break;
120
+ }
118
121
  return null;
119
122
  };
120
123
  }
@@ -124,9 +127,12 @@ cursor is in a syntax node with one of the given names.
124
127
  */
125
128
  function ifNotIn(nodes, source) {
126
129
  return (context) => {
127
- for (let pos = language.syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent)
130
+ for (let pos = language.syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
128
131
  if (nodes.indexOf(pos.name) > -1)
129
132
  return null;
133
+ if (pos.type.isTop)
134
+ break;
135
+ }
130
136
  return source(context);
131
137
  };
132
138
  }
@@ -231,13 +237,18 @@ class FuzzyMatcher {
231
237
  // For single-character queries, only match when they occur right
232
238
  // at the start
233
239
  if (chars.length == 1) {
234
- let first = state.codePointAt(word, 0);
235
- return first == chars[0] ? [0, 0, state.codePointSize(first)]
236
- : first == folded[0] ? [-200 /* Penalty.CaseFold */, 0, state.codePointSize(first)] : null;
240
+ let first = state.codePointAt(word, 0), firstSize = state.codePointSize(first);
241
+ let score = firstSize == word.length ? 0 : -100 /* Penalty.NotFull */;
242
+ if (first == chars[0]) ;
243
+ else if (first == folded[0])
244
+ score += -200 /* Penalty.CaseFold */;
245
+ else
246
+ return null;
247
+ return [score, 0, firstSize];
237
248
  }
238
249
  let direct = word.indexOf(this.pattern);
239
250
  if (direct == 0)
240
- return [0, 0, this.pattern.length];
251
+ return [word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */, 0, this.pattern.length];
241
252
  let len = chars.length, anyTo = 0;
242
253
  if (direct < 0) {
243
254
  for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
@@ -293,7 +304,7 @@ class FuzzyMatcher {
293
304
  if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
294
305
  return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0), byWord, word);
295
306
  if (adjacentTo == len && adjacentStart == 0)
296
- return [-200 /* Penalty.CaseFold */ - word.length, 0, adjacentEnd];
307
+ return [-200 /* Penalty.CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* Penalty.NotFull */), 0, adjacentEnd];
297
308
  if (direct > -1)
298
309
  return [-700 /* Penalty.NotStart */ - word.length, direct, direct + this.pattern.length];
299
310
  if (adjacentTo == len)
@@ -327,6 +338,7 @@ const completionConfig = state.Facet.define({
327
338
  closeOnBlur: true,
328
339
  maxRenderedOptions: 100,
329
340
  defaultKeymap: true,
341
+ tooltipClass: () => "",
330
342
  optionClass: () => "",
331
343
  aboveCursor: false,
332
344
  icons: true,
@@ -337,6 +349,7 @@ const completionConfig = state.Facet.define({
337
349
  defaultKeymap: (a, b) => a && b,
338
350
  closeOnBlur: (a, b) => a && b,
339
351
  icons: (a, b) => a && b,
352
+ tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
340
353
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
341
354
  addToOptions: (a, b) => a.concat(b)
342
355
  });
@@ -415,14 +428,17 @@ class CompletionTooltip {
415
428
  key: this
416
429
  };
417
430
  this.space = null;
431
+ this.currentClass = "";
418
432
  let cState = view.state.field(stateField);
419
433
  let { options, selected } = cState.open;
420
434
  let config = view.state.facet(completionConfig);
421
435
  this.optionContent = optionContent(config);
422
436
  this.optionClass = config.optionClass;
437
+ this.tooltipClass = config.tooltipClass;
423
438
  this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
424
439
  this.dom = document.createElement("div");
425
440
  this.dom.className = "cm-tooltip-autocomplete";
441
+ this.updateTooltipClass(view.state);
426
442
  this.dom.addEventListener("mousedown", (e) => {
427
443
  for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) {
428
444
  if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) {
@@ -443,12 +459,25 @@ class CompletionTooltip {
443
459
  var _a, _b, _c;
444
460
  let cState = update.state.field(this.stateField);
445
461
  let prevState = update.startState.field(this.stateField);
462
+ this.updateTooltipClass(update.state);
446
463
  if (cState != prevState) {
447
464
  this.updateSel();
448
465
  if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
449
466
  this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
450
467
  }
451
468
  }
469
+ updateTooltipClass(state) {
470
+ let cls = this.tooltipClass(state);
471
+ if (cls != this.currentClass) {
472
+ for (let c of this.currentClass.split(" "))
473
+ if (c)
474
+ this.dom.classList.remove(c);
475
+ for (let c of cls.split(" "))
476
+ if (c)
477
+ this.dom.classList.add(c);
478
+ this.currentClass = cls;
479
+ }
480
+ }
452
481
  positioned(space) {
453
482
  this.space = space;
454
483
  if (this.info)
@@ -709,13 +738,13 @@ class CompletionState {
709
738
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
710
739
  active = this.active;
711
740
  let open = this.open;
741
+ if (open && tr.docChanged)
742
+ open = open.map(tr.changes);
712
743
  if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
713
744
  !sameResults(active, this.active))
714
- open = CompletionDialog.build(active, state, this.id, this.open, conf);
745
+ open = CompletionDialog.build(active, state, this.id, open, conf);
715
746
  else if (open && open.disabled && !active.some(a => a.state == 1 /* State.Pending */))
716
747
  open = null;
717
- else if (open && tr.docChanged)
718
- open = open.map(tr.changes);
719
748
  if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
720
749
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
721
750
  for (let effect of tr.effects)
package/dist/index.d.ts CHANGED
@@ -50,6 +50,11 @@ interface CompletionConfig {
50
50
  */
51
51
  aboveCursor?: boolean;
52
52
  /**
53
+ When given, this may return an additional CSS class to add to
54
+ the completion dialog element.
55
+ */
56
+ tooltipClass?: (state: EditorState) => string;
57
+ /**
53
58
  This can be used to add additional CSS classes to completion
54
59
  options.
55
60
  */
package/dist/index.js CHANGED
@@ -108,9 +108,12 @@ cursor is in a syntax node with one of the given names.
108
108
  */
109
109
  function ifIn(nodes, source) {
110
110
  return (context) => {
111
- for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent)
111
+ for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
112
112
  if (nodes.indexOf(pos.name) > -1)
113
113
  return source(context);
114
+ if (pos.type.isTop)
115
+ break;
116
+ }
114
117
  return null;
115
118
  };
116
119
  }
@@ -120,9 +123,12 @@ cursor is in a syntax node with one of the given names.
120
123
  */
121
124
  function ifNotIn(nodes, source) {
122
125
  return (context) => {
123
- for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent)
126
+ for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
124
127
  if (nodes.indexOf(pos.name) > -1)
125
128
  return null;
129
+ if (pos.type.isTop)
130
+ break;
131
+ }
126
132
  return source(context);
127
133
  };
128
134
  }
@@ -227,13 +233,18 @@ class FuzzyMatcher {
227
233
  // For single-character queries, only match when they occur right
228
234
  // at the start
229
235
  if (chars.length == 1) {
230
- let first = codePointAt(word, 0);
231
- return first == chars[0] ? [0, 0, codePointSize(first)]
232
- : first == folded[0] ? [-200 /* Penalty.CaseFold */, 0, codePointSize(first)] : null;
236
+ let first = codePointAt(word, 0), firstSize = codePointSize(first);
237
+ let score = firstSize == word.length ? 0 : -100 /* Penalty.NotFull */;
238
+ if (first == chars[0]) ;
239
+ else if (first == folded[0])
240
+ score += -200 /* Penalty.CaseFold */;
241
+ else
242
+ return null;
243
+ return [score, 0, firstSize];
233
244
  }
234
245
  let direct = word.indexOf(this.pattern);
235
246
  if (direct == 0)
236
- return [0, 0, this.pattern.length];
247
+ return [word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */, 0, this.pattern.length];
237
248
  let len = chars.length, anyTo = 0;
238
249
  if (direct < 0) {
239
250
  for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
@@ -289,7 +300,7 @@ class FuzzyMatcher {
289
300
  if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
290
301
  return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0), byWord, word);
291
302
  if (adjacentTo == len && adjacentStart == 0)
292
- return [-200 /* Penalty.CaseFold */ - word.length, 0, adjacentEnd];
303
+ return [-200 /* Penalty.CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* Penalty.NotFull */), 0, adjacentEnd];
293
304
  if (direct > -1)
294
305
  return [-700 /* Penalty.NotStart */ - word.length, direct, direct + this.pattern.length];
295
306
  if (adjacentTo == len)
@@ -323,6 +334,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
323
334
  closeOnBlur: true,
324
335
  maxRenderedOptions: 100,
325
336
  defaultKeymap: true,
337
+ tooltipClass: () => "",
326
338
  optionClass: () => "",
327
339
  aboveCursor: false,
328
340
  icons: true,
@@ -333,6 +345,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
333
345
  defaultKeymap: (a, b) => a && b,
334
346
  closeOnBlur: (a, b) => a && b,
335
347
  icons: (a, b) => a && b,
348
+ tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
336
349
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
337
350
  addToOptions: (a, b) => a.concat(b)
338
351
  });
@@ -411,14 +424,17 @@ class CompletionTooltip {
411
424
  key: this
412
425
  };
413
426
  this.space = null;
427
+ this.currentClass = "";
414
428
  let cState = view.state.field(stateField);
415
429
  let { options, selected } = cState.open;
416
430
  let config = view.state.facet(completionConfig);
417
431
  this.optionContent = optionContent(config);
418
432
  this.optionClass = config.optionClass;
433
+ this.tooltipClass = config.tooltipClass;
419
434
  this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
420
435
  this.dom = document.createElement("div");
421
436
  this.dom.className = "cm-tooltip-autocomplete";
437
+ this.updateTooltipClass(view.state);
422
438
  this.dom.addEventListener("mousedown", (e) => {
423
439
  for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) {
424
440
  if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) {
@@ -439,12 +455,25 @@ class CompletionTooltip {
439
455
  var _a, _b, _c;
440
456
  let cState = update.state.field(this.stateField);
441
457
  let prevState = update.startState.field(this.stateField);
458
+ this.updateTooltipClass(update.state);
442
459
  if (cState != prevState) {
443
460
  this.updateSel();
444
461
  if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
445
462
  this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
446
463
  }
447
464
  }
465
+ updateTooltipClass(state) {
466
+ let cls = this.tooltipClass(state);
467
+ if (cls != this.currentClass) {
468
+ for (let c of this.currentClass.split(" "))
469
+ if (c)
470
+ this.dom.classList.remove(c);
471
+ for (let c of cls.split(" "))
472
+ if (c)
473
+ this.dom.classList.add(c);
474
+ this.currentClass = cls;
475
+ }
476
+ }
448
477
  positioned(space) {
449
478
  this.space = space;
450
479
  if (this.info)
@@ -705,13 +734,13 @@ class CompletionState {
705
734
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
706
735
  active = this.active;
707
736
  let open = this.open;
737
+ if (open && tr.docChanged)
738
+ open = open.map(tr.changes);
708
739
  if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
709
740
  !sameResults(active, this.active))
710
- open = CompletionDialog.build(active, state, this.id, this.open, conf);
741
+ open = CompletionDialog.build(active, state, this.id, open, conf);
711
742
  else if (open && open.disabled && !active.some(a => a.state == 1 /* State.Pending */))
712
743
  open = null;
713
- else if (open && tr.docChanged)
714
- open = open.map(tr.changes);
715
744
  if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
716
745
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
717
746
  for (let effect of tr.effects)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.3.4",
3
+ "version": "6.4.1",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "author": {
14
14
  "name": "Marijn Haverbeke",
15
- "email": "marijnh@gmail.com",
15
+ "email": "marijn@haverbeke.berlin",
16
16
  "url": "http://marijnhaverbeke.nl"
17
17
  },
18
18
  "type": "module",