@codemirror/lint 6.8.3 → 6.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 6.8.5 (2025-03-26)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a regression (since 6.8.4) that broke the `markerFilter` option.
6
+
7
+ ## 6.8.4 (2024-11-28)
8
+
9
+ ### Bug fixes
10
+
11
+ Don't create overlapping decorations when diagnostics overlap.
12
+
13
+ Fix an issue where block widgets could cause the lint gutter to show diagnostics multiple times.
14
+
1
15
  ## 6.8.3 (2024-11-21)
2
16
 
3
17
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -17,34 +17,75 @@ class LintState {
17
17
  this.panel = panel;
18
18
  this.selected = selected;
19
19
  }
20
- static init(diagnostics, panel, state) {
20
+ static init(diagnostics, panel, state$1) {
21
21
  // Filter the list of diagnostics for which to create markers
22
- let markedDiagnostics = diagnostics;
23
- let diagnosticFilter = state.facet(lintConfig).markerFilter;
22
+ let diagnosticFilter = state$1.facet(lintConfig).markerFilter;
24
23
  if (diagnosticFilter)
25
- markedDiagnostics = diagnosticFilter(markedDiagnostics, state);
26
- let ranges = view.Decoration.set(markedDiagnostics.map((d) => {
27
- // For zero-length ranges or ranges covering only a line break, create a widget
28
- return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
29
- ? view.Decoration.widget({
30
- widget: new DiagnosticWidget(d),
31
- diagnostic: d
32
- }).range(d.from)
33
- : view.Decoration.mark({
34
- attributes: { class: "cm-lintRange cm-lintRange-" + d.severity + (d.markClass ? " " + d.markClass : "") },
35
- diagnostic: d
36
- }).range(d.from, d.to);
37
- }), true);
38
- return new LintState(ranges, panel, findDiagnostic(ranges));
24
+ diagnostics = diagnosticFilter(diagnostics, state$1);
25
+ let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
26
+ let deco = new state.RangeSetBuilder(), active = [], pos = 0;
27
+ for (let i = 0;;) {
28
+ let next = i == sorted.length ? null : sorted[i];
29
+ if (!next && !active.length)
30
+ break;
31
+ let from, to;
32
+ if (active.length) {
33
+ from = pos;
34
+ to = active.reduce((p, d) => Math.min(p, d.to), next && next.from > from ? next.from : 1e8);
35
+ }
36
+ else {
37
+ from = next.from;
38
+ to = next.to;
39
+ active.push(next);
40
+ i++;
41
+ }
42
+ while (i < sorted.length) {
43
+ let next = sorted[i];
44
+ if (next.from == from && (next.to > next.from || next.to == from)) {
45
+ active.push(next);
46
+ i++;
47
+ to = Math.min(next.to, to);
48
+ }
49
+ else {
50
+ to = Math.min(next.from, to);
51
+ break;
52
+ }
53
+ }
54
+ let sev = maxSeverity(active);
55
+ if (active.some(d => d.from == d.to || (d.from == d.to - 1 && state$1.doc.lineAt(d.from).to == d.from))) {
56
+ deco.add(from, from, view.Decoration.widget({
57
+ widget: new DiagnosticWidget(sev),
58
+ diagnostics: active.slice()
59
+ }));
60
+ }
61
+ else {
62
+ let markClass = active.reduce((c, d) => d.markClass ? c + " " + d.markClass : c, "");
63
+ deco.add(from, to, view.Decoration.mark({
64
+ class: "cm-lintRange cm-lintRange-" + sev + markClass,
65
+ diagnostics: active.slice(),
66
+ inclusiveEnd: active.some(a => a.to > to)
67
+ }));
68
+ }
69
+ pos = to;
70
+ for (let i = 0; i < active.length; i++)
71
+ if (active[i].to <= pos)
72
+ active.splice(i--, 1);
73
+ }
74
+ let set = deco.finish();
75
+ return new LintState(set, panel, findDiagnostic(set));
39
76
  }
40
77
  }
41
78
  function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
42
79
  let found = null;
43
80
  diagnostics.between(after, 1e9, (from, to, { spec }) => {
44
- if (diagnostic && spec.diagnostic != diagnostic)
81
+ if (diagnostic && spec.diagnostics.indexOf(diagnostic) < 0)
45
82
  return;
46
- found = new SelectedDiagnostic(from, to, spec.diagnostic);
47
- return false;
83
+ if (!found)
84
+ found = new SelectedDiagnostic(from, to, diagnostic || spec.diagnostics[0]);
85
+ else if (spec.diagnostics.indexOf(found.diagnostic) < 0)
86
+ return false;
87
+ else
88
+ found = new SelectedDiagnostic(found.from, to, found.diagnostic);
48
89
  });
49
90
  return found;
50
91
  }
@@ -118,24 +159,25 @@ function diagnosticCount(state) {
118
159
  const activeMark = view.Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
119
160
  function lintTooltip(view, pos, side) {
120
161
  let { diagnostics } = view.state.field(lintState);
121
- let found = [], stackStart = 2e8, stackEnd = 0;
162
+ let found, start = -1, end = -1;
122
163
  diagnostics.between(pos - (side < 0 ? 1 : 0), pos + (side > 0 ? 1 : 0), (from, to, { spec }) => {
123
164
  if (pos >= from && pos <= to &&
124
165
  (from == to || ((pos > from || side > 0) && (pos < to || side < 0)))) {
125
- found.push(spec.diagnostic);
126
- stackStart = Math.min(from, stackStart);
127
- stackEnd = Math.max(to, stackEnd);
166
+ found = spec.diagnostics;
167
+ start = from;
168
+ end = to;
169
+ return false;
128
170
  }
129
171
  });
130
172
  let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
131
- if (diagnosticFilter)
173
+ if (found && diagnosticFilter)
132
174
  found = diagnosticFilter(found, view.state);
133
- if (!found.length)
175
+ if (!found)
134
176
  return null;
135
177
  return {
136
- pos: stackStart,
137
- end: stackEnd,
138
- above: view.state.doc.lineAt(stackStart).to < stackEnd,
178
+ pos: start,
179
+ end: end,
180
+ above: view.state.doc.lineAt(start).to < end,
139
181
  create() {
140
182
  return { dom: diagnosticsTooltip(view, found) };
141
183
  }
@@ -272,7 +314,7 @@ function batchResults(promises, sink, error) {
272
314
  if (collected.length == promises.length)
273
315
  sink(collected);
274
316
  else
275
- setTimeout(() => sink(collected), 200);
317
+ timeout = setTimeout(() => sink(collected), 200);
276
318
  }, error);
277
319
  }
278
320
  const lintConfig = state.Facet.define({
@@ -352,13 +394,13 @@ function renderDiagnostic(view, diagnostic, inPanel) {
352
394
  }), diagnostic.source && elt("div", { class: "cm-diagnosticSource" }, diagnostic.source));
353
395
  }
354
396
  class DiagnosticWidget extends view.WidgetType {
355
- constructor(diagnostic) {
397
+ constructor(sev) {
356
398
  super();
357
- this.diagnostic = diagnostic;
399
+ this.sev = sev;
358
400
  }
359
- eq(other) { return other.diagnostic == this.diagnostic; }
401
+ eq(other) { return other.sev == this.sev; }
360
402
  toDOM() {
361
- return elt("span", { class: "cm-lintPoint cm-lintPoint-" + this.diagnostic.severity });
403
+ return elt("span", { class: "cm-lintPoint cm-lintPoint-" + this.sev });
362
404
  }
363
405
  }
364
406
  class PanelItem {
@@ -441,35 +483,41 @@ class LintPanel {
441
483
  update() {
442
484
  let { diagnostics, selected } = this.view.state.field(lintState);
443
485
  let i = 0, needsSync = false, newSelectedItem = null;
486
+ let seen = new Set();
444
487
  diagnostics.between(0, this.view.state.doc.length, (_start, _end, { spec }) => {
445
- let found = -1, item;
446
- for (let j = i; j < this.items.length; j++)
447
- if (this.items[j].diagnostic == spec.diagnostic) {
448
- found = j;
449
- break;
450
- }
451
- if (found < 0) {
452
- item = new PanelItem(this.view, spec.diagnostic);
453
- this.items.splice(i, 0, item);
454
- needsSync = true;
455
- }
456
- else {
457
- item = this.items[found];
458
- if (found > i) {
459
- this.items.splice(i, found - i);
488
+ for (let diagnostic of spec.diagnostics) {
489
+ if (seen.has(diagnostic))
490
+ continue;
491
+ seen.add(diagnostic);
492
+ let found = -1, item;
493
+ for (let j = i; j < this.items.length; j++)
494
+ if (this.items[j].diagnostic == diagnostic) {
495
+ found = j;
496
+ break;
497
+ }
498
+ if (found < 0) {
499
+ item = new PanelItem(this.view, diagnostic);
500
+ this.items.splice(i, 0, item);
460
501
  needsSync = true;
461
502
  }
462
- }
463
- if (selected && item.diagnostic == selected.diagnostic) {
464
- if (!item.dom.hasAttribute("aria-selected")) {
465
- item.dom.setAttribute("aria-selected", "true");
466
- newSelectedItem = item;
503
+ else {
504
+ item = this.items[found];
505
+ if (found > i) {
506
+ this.items.splice(i, found - i);
507
+ needsSync = true;
508
+ }
467
509
  }
510
+ if (selected && item.diagnostic == selected.diagnostic) {
511
+ if (!item.dom.hasAttribute("aria-selected")) {
512
+ item.dom.setAttribute("aria-selected", "true");
513
+ newSelectedItem = item;
514
+ }
515
+ }
516
+ else if (item.dom.hasAttribute("aria-selected")) {
517
+ item.dom.removeAttribute("aria-selected");
518
+ }
519
+ i++;
468
520
  }
469
- else if (item.dom.hasAttribute("aria-selected")) {
470
- item.dom.removeAttribute("aria-selected");
471
- }
472
- i++;
473
521
  });
474
522
  while (i < this.items.length && !(this.items.length == 1 && this.items[0].diagnostic.from < 0)) {
475
523
  needsSync = true;
@@ -638,11 +686,22 @@ const baseTheme = view.EditorView.baseTheme({
638
686
  function severityWeight(sev) {
639
687
  return sev == "error" ? 4 : sev == "warning" ? 3 : sev == "info" ? 2 : 1;
640
688
  }
689
+ function maxSeverity(diagnostics) {
690
+ let sev = "hint", weight = 1;
691
+ for (let d of diagnostics) {
692
+ let w = severityWeight(d.severity);
693
+ if (w > weight) {
694
+ weight = w;
695
+ sev = d.severity;
696
+ }
697
+ }
698
+ return sev;
699
+ }
641
700
  class LintGutterMarker extends view.GutterMarker {
642
701
  constructor(diagnostics) {
643
702
  super();
644
703
  this.diagnostics = diagnostics;
645
- this.severity = diagnostics.reduce((max, d) => severityWeight(max) < severityWeight(d.severity) ? d.severity : max, "hint");
704
+ this.severity = maxSeverity(diagnostics);
646
705
  }
647
706
  toDOM(view) {
648
707
  let elt = document.createElement("div");
@@ -721,7 +780,8 @@ const lintGutterExtension = view.gutter({
721
780
  widgetMarker: (view, widget, block) => {
722
781
  let diagnostics = [];
723
782
  view.state.field(lintGutterMarkers).between(block.from, block.to, (from, to, value) => {
724
- diagnostics.push(...value.diagnostics);
783
+ if (from > block.from && from < block.to)
784
+ diagnostics.push(...value.diagnostics);
725
785
  });
726
786
  return diagnostics.length ? new LintGutterMarker(diagnostics) : null;
727
787
  }
@@ -812,9 +872,25 @@ arguments hold the diagnostic's current position.
812
872
  */
813
873
  function forEachDiagnostic(state$1, f) {
814
874
  let lState = state$1.field(lintState, false);
815
- if (lState && lState.diagnostics.size)
816
- for (let iter = state.RangeSet.iter([lState.diagnostics]); iter.value; iter.next())
817
- f(iter.value.spec.diagnostic, iter.from, iter.to);
875
+ if (lState && lState.diagnostics.size) {
876
+ let pending = [], pendingStart = [], lastEnd = -1;
877
+ for (let iter = state.RangeSet.iter([lState.diagnostics]);; iter.next()) {
878
+ for (let i = 0; i < pending.length; i++)
879
+ if (!iter.value || iter.value.spec.diagnostics.indexOf(pending[i]) < 0) {
880
+ f(pending[i], pendingStart[i], lastEnd);
881
+ pending.splice(i, 1);
882
+ pendingStart.splice(i--, 1);
883
+ }
884
+ if (!iter.value)
885
+ break;
886
+ for (let d of iter.value.spec.diagnostics)
887
+ if (pending.indexOf(d) < 0) {
888
+ pending.push(d);
889
+ pendingStart.push(iter.from);
890
+ }
891
+ lastEnd = iter.to;
892
+ }
893
+ }
818
894
  }
819
895
 
820
896
  exports.closeLintPanel = closeLintPanel;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Decoration, showPanel, EditorView, ViewPlugin, gutter, showTooltip, hoverTooltip, getPanel, logException, WidgetType, GutterMarker } from '@codemirror/view';
2
- import { StateEffect, StateField, Facet, combineConfig, RangeSet } from '@codemirror/state';
2
+ import { StateEffect, StateField, Facet, combineConfig, RangeSet, RangeSetBuilder } from '@codemirror/state';
3
3
  import elt from 'crelt';
4
4
 
5
5
  class SelectedDiagnostic {
@@ -17,32 +17,73 @@ class LintState {
17
17
  }
18
18
  static init(diagnostics, panel, state) {
19
19
  // Filter the list of diagnostics for which to create markers
20
- let markedDiagnostics = diagnostics;
21
20
  let diagnosticFilter = state.facet(lintConfig).markerFilter;
22
21
  if (diagnosticFilter)
23
- markedDiagnostics = diagnosticFilter(markedDiagnostics, state);
24
- let ranges = Decoration.set(markedDiagnostics.map((d) => {
25
- // For zero-length ranges or ranges covering only a line break, create a widget
26
- return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
27
- ? Decoration.widget({
28
- widget: new DiagnosticWidget(d),
29
- diagnostic: d
30
- }).range(d.from)
31
- : Decoration.mark({
32
- attributes: { class: "cm-lintRange cm-lintRange-" + d.severity + (d.markClass ? " " + d.markClass : "") },
33
- diagnostic: d
34
- }).range(d.from, d.to);
35
- }), true);
36
- return new LintState(ranges, panel, findDiagnostic(ranges));
22
+ diagnostics = diagnosticFilter(diagnostics, state);
23
+ let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
24
+ let deco = new RangeSetBuilder(), active = [], pos = 0;
25
+ for (let i = 0;;) {
26
+ let next = i == sorted.length ? null : sorted[i];
27
+ if (!next && !active.length)
28
+ break;
29
+ let from, to;
30
+ if (active.length) {
31
+ from = pos;
32
+ to = active.reduce((p, d) => Math.min(p, d.to), next && next.from > from ? next.from : 1e8);
33
+ }
34
+ else {
35
+ from = next.from;
36
+ to = next.to;
37
+ active.push(next);
38
+ i++;
39
+ }
40
+ while (i < sorted.length) {
41
+ let next = sorted[i];
42
+ if (next.from == from && (next.to > next.from || next.to == from)) {
43
+ active.push(next);
44
+ i++;
45
+ to = Math.min(next.to, to);
46
+ }
47
+ else {
48
+ to = Math.min(next.from, to);
49
+ break;
50
+ }
51
+ }
52
+ let sev = maxSeverity(active);
53
+ if (active.some(d => d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from))) {
54
+ deco.add(from, from, Decoration.widget({
55
+ widget: new DiagnosticWidget(sev),
56
+ diagnostics: active.slice()
57
+ }));
58
+ }
59
+ else {
60
+ let markClass = active.reduce((c, d) => d.markClass ? c + " " + d.markClass : c, "");
61
+ deco.add(from, to, Decoration.mark({
62
+ class: "cm-lintRange cm-lintRange-" + sev + markClass,
63
+ diagnostics: active.slice(),
64
+ inclusiveEnd: active.some(a => a.to > to)
65
+ }));
66
+ }
67
+ pos = to;
68
+ for (let i = 0; i < active.length; i++)
69
+ if (active[i].to <= pos)
70
+ active.splice(i--, 1);
71
+ }
72
+ let set = deco.finish();
73
+ return new LintState(set, panel, findDiagnostic(set));
37
74
  }
38
75
  }
39
76
  function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
40
77
  let found = null;
41
78
  diagnostics.between(after, 1e9, (from, to, { spec }) => {
42
- if (diagnostic && spec.diagnostic != diagnostic)
79
+ if (diagnostic && spec.diagnostics.indexOf(diagnostic) < 0)
43
80
  return;
44
- found = new SelectedDiagnostic(from, to, spec.diagnostic);
45
- return false;
81
+ if (!found)
82
+ found = new SelectedDiagnostic(from, to, diagnostic || spec.diagnostics[0]);
83
+ else if (spec.diagnostics.indexOf(found.diagnostic) < 0)
84
+ return false;
85
+ else
86
+ found = new SelectedDiagnostic(found.from, to, found.diagnostic);
46
87
  });
47
88
  return found;
48
89
  }
@@ -116,24 +157,25 @@ function diagnosticCount(state) {
116
157
  const activeMark = /*@__PURE__*/Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
117
158
  function lintTooltip(view, pos, side) {
118
159
  let { diagnostics } = view.state.field(lintState);
119
- let found = [], stackStart = 2e8, stackEnd = 0;
160
+ let found, start = -1, end = -1;
120
161
  diagnostics.between(pos - (side < 0 ? 1 : 0), pos + (side > 0 ? 1 : 0), (from, to, { spec }) => {
121
162
  if (pos >= from && pos <= to &&
122
163
  (from == to || ((pos > from || side > 0) && (pos < to || side < 0)))) {
123
- found.push(spec.diagnostic);
124
- stackStart = Math.min(from, stackStart);
125
- stackEnd = Math.max(to, stackEnd);
164
+ found = spec.diagnostics;
165
+ start = from;
166
+ end = to;
167
+ return false;
126
168
  }
127
169
  });
128
170
  let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
129
- if (diagnosticFilter)
171
+ if (found && diagnosticFilter)
130
172
  found = diagnosticFilter(found, view.state);
131
- if (!found.length)
173
+ if (!found)
132
174
  return null;
133
175
  return {
134
- pos: stackStart,
135
- end: stackEnd,
136
- above: view.state.doc.lineAt(stackStart).to < stackEnd,
176
+ pos: start,
177
+ end: end,
178
+ above: view.state.doc.lineAt(start).to < end,
137
179
  create() {
138
180
  return { dom: diagnosticsTooltip(view, found) };
139
181
  }
@@ -270,7 +312,7 @@ function batchResults(promises, sink, error) {
270
312
  if (collected.length == promises.length)
271
313
  sink(collected);
272
314
  else
273
- setTimeout(() => sink(collected), 200);
315
+ timeout = setTimeout(() => sink(collected), 200);
274
316
  }, error);
275
317
  }
276
318
  const lintConfig = /*@__PURE__*/Facet.define({
@@ -350,13 +392,13 @@ function renderDiagnostic(view, diagnostic, inPanel) {
350
392
  }), diagnostic.source && elt("div", { class: "cm-diagnosticSource" }, diagnostic.source));
351
393
  }
352
394
  class DiagnosticWidget extends WidgetType {
353
- constructor(diagnostic) {
395
+ constructor(sev) {
354
396
  super();
355
- this.diagnostic = diagnostic;
397
+ this.sev = sev;
356
398
  }
357
- eq(other) { return other.diagnostic == this.diagnostic; }
399
+ eq(other) { return other.sev == this.sev; }
358
400
  toDOM() {
359
- return elt("span", { class: "cm-lintPoint cm-lintPoint-" + this.diagnostic.severity });
401
+ return elt("span", { class: "cm-lintPoint cm-lintPoint-" + this.sev });
360
402
  }
361
403
  }
362
404
  class PanelItem {
@@ -439,35 +481,41 @@ class LintPanel {
439
481
  update() {
440
482
  let { diagnostics, selected } = this.view.state.field(lintState);
441
483
  let i = 0, needsSync = false, newSelectedItem = null;
484
+ let seen = new Set();
442
485
  diagnostics.between(0, this.view.state.doc.length, (_start, _end, { spec }) => {
443
- let found = -1, item;
444
- for (let j = i; j < this.items.length; j++)
445
- if (this.items[j].diagnostic == spec.diagnostic) {
446
- found = j;
447
- break;
448
- }
449
- if (found < 0) {
450
- item = new PanelItem(this.view, spec.diagnostic);
451
- this.items.splice(i, 0, item);
452
- needsSync = true;
453
- }
454
- else {
455
- item = this.items[found];
456
- if (found > i) {
457
- this.items.splice(i, found - i);
486
+ for (let diagnostic of spec.diagnostics) {
487
+ if (seen.has(diagnostic))
488
+ continue;
489
+ seen.add(diagnostic);
490
+ let found = -1, item;
491
+ for (let j = i; j < this.items.length; j++)
492
+ if (this.items[j].diagnostic == diagnostic) {
493
+ found = j;
494
+ break;
495
+ }
496
+ if (found < 0) {
497
+ item = new PanelItem(this.view, diagnostic);
498
+ this.items.splice(i, 0, item);
458
499
  needsSync = true;
459
500
  }
460
- }
461
- if (selected && item.diagnostic == selected.diagnostic) {
462
- if (!item.dom.hasAttribute("aria-selected")) {
463
- item.dom.setAttribute("aria-selected", "true");
464
- newSelectedItem = item;
501
+ else {
502
+ item = this.items[found];
503
+ if (found > i) {
504
+ this.items.splice(i, found - i);
505
+ needsSync = true;
506
+ }
465
507
  }
508
+ if (selected && item.diagnostic == selected.diagnostic) {
509
+ if (!item.dom.hasAttribute("aria-selected")) {
510
+ item.dom.setAttribute("aria-selected", "true");
511
+ newSelectedItem = item;
512
+ }
513
+ }
514
+ else if (item.dom.hasAttribute("aria-selected")) {
515
+ item.dom.removeAttribute("aria-selected");
516
+ }
517
+ i++;
466
518
  }
467
- else if (item.dom.hasAttribute("aria-selected")) {
468
- item.dom.removeAttribute("aria-selected");
469
- }
470
- i++;
471
519
  });
472
520
  while (i < this.items.length && !(this.items.length == 1 && this.items[0].diagnostic.from < 0)) {
473
521
  needsSync = true;
@@ -636,11 +684,22 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
636
684
  function severityWeight(sev) {
637
685
  return sev == "error" ? 4 : sev == "warning" ? 3 : sev == "info" ? 2 : 1;
638
686
  }
687
+ function maxSeverity(diagnostics) {
688
+ let sev = "hint", weight = 1;
689
+ for (let d of diagnostics) {
690
+ let w = severityWeight(d.severity);
691
+ if (w > weight) {
692
+ weight = w;
693
+ sev = d.severity;
694
+ }
695
+ }
696
+ return sev;
697
+ }
639
698
  class LintGutterMarker extends GutterMarker {
640
699
  constructor(diagnostics) {
641
700
  super();
642
701
  this.diagnostics = diagnostics;
643
- this.severity = diagnostics.reduce((max, d) => severityWeight(max) < severityWeight(d.severity) ? d.severity : max, "hint");
702
+ this.severity = maxSeverity(diagnostics);
644
703
  }
645
704
  toDOM(view) {
646
705
  let elt = document.createElement("div");
@@ -719,7 +778,8 @@ const lintGutterExtension = /*@__PURE__*/gutter({
719
778
  widgetMarker: (view, widget, block) => {
720
779
  let diagnostics = [];
721
780
  view.state.field(lintGutterMarkers).between(block.from, block.to, (from, to, value) => {
722
- diagnostics.push(...value.diagnostics);
781
+ if (from > block.from && from < block.to)
782
+ diagnostics.push(...value.diagnostics);
723
783
  });
724
784
  return diagnostics.length ? new LintGutterMarker(diagnostics) : null;
725
785
  }
@@ -810,9 +870,25 @@ arguments hold the diagnostic's current position.
810
870
  */
811
871
  function forEachDiagnostic(state, f) {
812
872
  let lState = state.field(lintState, false);
813
- if (lState && lState.diagnostics.size)
814
- for (let iter = RangeSet.iter([lState.diagnostics]); iter.value; iter.next())
815
- f(iter.value.spec.diagnostic, iter.from, iter.to);
873
+ if (lState && lState.diagnostics.size) {
874
+ let pending = [], pendingStart = [], lastEnd = -1;
875
+ for (let iter = RangeSet.iter([lState.diagnostics]);; iter.next()) {
876
+ for (let i = 0; i < pending.length; i++)
877
+ if (!iter.value || iter.value.spec.diagnostics.indexOf(pending[i]) < 0) {
878
+ f(pending[i], pendingStart[i], lastEnd);
879
+ pending.splice(i, 1);
880
+ pendingStart.splice(i--, 1);
881
+ }
882
+ if (!iter.value)
883
+ break;
884
+ for (let d of iter.value.spec.diagnostics)
885
+ if (pending.indexOf(d) < 0) {
886
+ pending.push(d);
887
+ pendingStart.push(iter.from);
888
+ }
889
+ lastEnd = iter.to;
890
+ }
891
+ }
816
892
  }
817
893
 
818
894
  export { closeLintPanel, diagnosticCount, forEachDiagnostic, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, previousDiagnostic, setDiagnostics, setDiagnosticsEffect };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/lint",
3
- "version": "6.8.3",
3
+ "version": "6.8.5",
4
4
  "description": "Linting support for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",