@codemirror/lint 6.8.5 → 6.9.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,19 @@
1
+ ## 6.9.1 (2025-10-23)
2
+
3
+ ### Bug fixes
4
+
5
+ Properly display diagnostics that just cover multiple newlines as widgets.
6
+
7
+ ## 6.9.0 (2025-10-02)
8
+
9
+ ### Bug fixes
10
+
11
+ Multiple configurations to `linter` will now be merged without raising an error.
12
+
13
+ ### New features
14
+
15
+ The new `markClass` option to actions makes it possible to style action buttons.
16
+
1
17
  ## 6.8.5 (2025-03-26)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ class LintState {
24
24
  diagnostics = diagnosticFilter(diagnostics, state$1);
25
25
  let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
26
26
  let deco = new state.RangeSetBuilder(), active = [], pos = 0;
27
+ let scan = state$1.doc.iter(), scanPos = 0;
27
28
  for (let i = 0;;) {
28
29
  let next = i == sorted.length ? null : sorted[i];
29
30
  if (!next && !active.length)
@@ -51,8 +52,30 @@ class LintState {
51
52
  break;
52
53
  }
53
54
  }
55
+ let widget = false;
56
+ if (active.some(d => d.from == from && d.to == to)) {
57
+ widget = from == to;
58
+ if (!widget && to - from < 10) {
59
+ let behind = from - (scanPos + scan.value.length);
60
+ if (behind > 0) {
61
+ scan.next(behind);
62
+ scanPos = from;
63
+ }
64
+ for (let check = from;;) {
65
+ if (check >= to) {
66
+ widget = true;
67
+ break;
68
+ }
69
+ if (!scan.lineBreak && scanPos + scan.value.length > check)
70
+ break;
71
+ check = scanPos + scan.value.length;
72
+ scanPos += scan.value.length;
73
+ scan.next();
74
+ }
75
+ }
76
+ }
54
77
  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))) {
78
+ if (widget) {
56
79
  deco.add(from, from, view.Decoration.widget({
57
80
  widget: new DiagnosticWidget(sev),
58
81
  diagnostics: active.slice()
@@ -319,22 +342,36 @@ function batchResults(promises, sink, error) {
319
342
  }
320
343
  const lintConfig = state.Facet.define({
321
344
  combine(input) {
322
- return Object.assign({ sources: input.map(i => i.source).filter(x => x != null) }, state.combineConfig(input.map(i => i.config), {
323
- delay: 750,
324
- markerFilter: null,
325
- tooltipFilter: null,
326
- needsRefresh: null,
327
- hideOn: () => null,
328
- }, {
329
- needsRefresh: (a, b) => !a ? b : !b ? a : u => a(u) || b(u)
330
- }));
345
+ return {
346
+ sources: input.map(i => i.source).filter(x => x != null),
347
+ ...state.combineConfig(input.map(i => i.config), {
348
+ delay: 750,
349
+ markerFilter: null,
350
+ tooltipFilter: null,
351
+ needsRefresh: null,
352
+ hideOn: () => null,
353
+ }, {
354
+ delay: Math.max,
355
+ markerFilter: combineFilter,
356
+ tooltipFilter: combineFilter,
357
+ needsRefresh: (a, b) => !a ? b : !b ? a : u => a(u) || b(u),
358
+ hideOn: (a, b) => !a ? b : !b ? a : (t, x, y) => a(t, x, y) || b(t, x, y),
359
+ autoPanel: (a, b) => a || b
360
+ })
361
+ };
331
362
  }
332
363
  });
364
+ function combineFilter(a, b) {
365
+ return !a ? b : !b ? a : (d, s) => b(a(d, s), s);
366
+ }
333
367
  /**
334
368
  Given a diagnostic source, this function returns an extension that
335
369
  enables linting with that source. It will be called whenever the
336
- editor is idle (after its content changed). If `null` is given as
337
- source, this only configures the lint extension.
370
+ editor is idle (after its content changed).
371
+
372
+ Note that settings given here will apply to all linters active in
373
+ the editor. If `null` is given as source, this only configures the
374
+ lint extension.
338
375
  */
339
376
  function linter(source, config = {}) {
340
377
  return [
@@ -384,9 +421,10 @@ function renderDiagnostic(view, diagnostic, inPanel) {
384
421
  let nameElt = keyIndex < 0 ? name : [name.slice(0, keyIndex),
385
422
  elt("u", name.slice(keyIndex, keyIndex + 1)),
386
423
  name.slice(keyIndex + 1)];
424
+ let markClass = action.markClass ? " " + action.markClass : "";
387
425
  return elt("button", {
388
426
  type: "button",
389
- class: "cm-diagnosticAction",
427
+ class: "cm-diagnosticAction" + markClass,
390
428
  onclick: click,
391
429
  onmousedown: click,
392
430
  "aria-label": ` Action: ${name}${keyIndex < 0 ? "" : ` (access key "${keys[i]})"`}.`
@@ -809,7 +847,7 @@ const lintGutterTooltip = state.StateField.define({
809
847
  create() { return null; },
810
848
  update(tooltip, tr) {
811
849
  if (tooltip && tr.docChanged)
812
- tooltip = hideTooltip(tr, tooltip) ? null : Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
850
+ tooltip = hideTooltip(tr, tooltip) ? null : { ...tooltip, pos: tr.changes.mapPos(tooltip.pos) };
813
851
  return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
814
852
  },
815
853
  provide: field => view.showTooltip.from(field)
package/dist/index.d.cts CHANGED
@@ -56,6 +56,10 @@ interface Action {
56
56
  */
57
57
  name: string;
58
58
  /**
59
+ When given, add an extra CSS class to the action button.
60
+ */
61
+ markClass?: string;
62
+ /**
59
63
  The function to call when the user activates this action. Is
60
64
  given the diagnostic's _current_ position, which may have
61
65
  changed since the creation of the diagnostic, due to editing.
@@ -161,8 +165,11 @@ type LintSource = (view: EditorView) => readonly Diagnostic[] | Promise<readonly
161
165
  /**
162
166
  Given a diagnostic source, this function returns an extension that
163
167
  enables linting with that source. It will be called whenever the
164
- editor is idle (after its content changed). If `null` is given as
165
- source, this only configures the lint extension.
168
+ editor is idle (after its content changed).
169
+
170
+ Note that settings given here will apply to all linters active in
171
+ the editor. If `null` is given as source, this only configures the
172
+ lint extension.
166
173
  */
167
174
  declare function linter(source: LintSource | null, config?: LintConfig): Extension;
168
175
  /**
package/dist/index.d.ts CHANGED
@@ -56,6 +56,10 @@ interface Action {
56
56
  */
57
57
  name: string;
58
58
  /**
59
+ When given, add an extra CSS class to the action button.
60
+ */
61
+ markClass?: string;
62
+ /**
59
63
  The function to call when the user activates this action. Is
60
64
  given the diagnostic's _current_ position, which may have
61
65
  changed since the creation of the diagnostic, due to editing.
@@ -161,8 +165,11 @@ type LintSource = (view: EditorView) => readonly Diagnostic[] | Promise<readonly
161
165
  /**
162
166
  Given a diagnostic source, this function returns an extension that
163
167
  enables linting with that source. It will be called whenever the
164
- editor is idle (after its content changed). If `null` is given as
165
- source, this only configures the lint extension.
168
+ editor is idle (after its content changed).
169
+
170
+ Note that settings given here will apply to all linters active in
171
+ the editor. If `null` is given as source, this only configures the
172
+ lint extension.
166
173
  */
167
174
  declare function linter(source: LintSource | null, config?: LintConfig): Extension;
168
175
  /**
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ class LintState {
22
22
  diagnostics = diagnosticFilter(diagnostics, state);
23
23
  let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
24
24
  let deco = new RangeSetBuilder(), active = [], pos = 0;
25
+ let scan = state.doc.iter(), scanPos = 0;
25
26
  for (let i = 0;;) {
26
27
  let next = i == sorted.length ? null : sorted[i];
27
28
  if (!next && !active.length)
@@ -49,8 +50,30 @@ class LintState {
49
50
  break;
50
51
  }
51
52
  }
53
+ let widget = false;
54
+ if (active.some(d => d.from == from && d.to == to)) {
55
+ widget = from == to;
56
+ if (!widget && to - from < 10) {
57
+ let behind = from - (scanPos + scan.value.length);
58
+ if (behind > 0) {
59
+ scan.next(behind);
60
+ scanPos = from;
61
+ }
62
+ for (let check = from;;) {
63
+ if (check >= to) {
64
+ widget = true;
65
+ break;
66
+ }
67
+ if (!scan.lineBreak && scanPos + scan.value.length > check)
68
+ break;
69
+ check = scanPos + scan.value.length;
70
+ scanPos += scan.value.length;
71
+ scan.next();
72
+ }
73
+ }
74
+ }
52
75
  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))) {
76
+ if (widget) {
54
77
  deco.add(from, from, Decoration.widget({
55
78
  widget: new DiagnosticWidget(sev),
56
79
  diagnostics: active.slice()
@@ -317,22 +340,36 @@ function batchResults(promises, sink, error) {
317
340
  }
318
341
  const lintConfig = /*@__PURE__*/Facet.define({
319
342
  combine(input) {
320
- return Object.assign({ sources: input.map(i => i.source).filter(x => x != null) }, combineConfig(input.map(i => i.config), {
321
- delay: 750,
322
- markerFilter: null,
323
- tooltipFilter: null,
324
- needsRefresh: null,
325
- hideOn: () => null,
326
- }, {
327
- needsRefresh: (a, b) => !a ? b : !b ? a : u => a(u) || b(u)
328
- }));
343
+ return {
344
+ sources: input.map(i => i.source).filter(x => x != null),
345
+ ...combineConfig(input.map(i => i.config), {
346
+ delay: 750,
347
+ markerFilter: null,
348
+ tooltipFilter: null,
349
+ needsRefresh: null,
350
+ hideOn: () => null,
351
+ }, {
352
+ delay: Math.max,
353
+ markerFilter: combineFilter,
354
+ tooltipFilter: combineFilter,
355
+ needsRefresh: (a, b) => !a ? b : !b ? a : u => a(u) || b(u),
356
+ hideOn: (a, b) => !a ? b : !b ? a : (t, x, y) => a(t, x, y) || b(t, x, y),
357
+ autoPanel: (a, b) => a || b
358
+ })
359
+ };
329
360
  }
330
361
  });
362
+ function combineFilter(a, b) {
363
+ return !a ? b : !b ? a : (d, s) => b(a(d, s), s);
364
+ }
331
365
  /**
332
366
  Given a diagnostic source, this function returns an extension that
333
367
  enables linting with that source. It will be called whenever the
334
- editor is idle (after its content changed). If `null` is given as
335
- source, this only configures the lint extension.
368
+ editor is idle (after its content changed).
369
+
370
+ Note that settings given here will apply to all linters active in
371
+ the editor. If `null` is given as source, this only configures the
372
+ lint extension.
336
373
  */
337
374
  function linter(source, config = {}) {
338
375
  return [
@@ -382,9 +419,10 @@ function renderDiagnostic(view, diagnostic, inPanel) {
382
419
  let nameElt = keyIndex < 0 ? name : [name.slice(0, keyIndex),
383
420
  elt("u", name.slice(keyIndex, keyIndex + 1)),
384
421
  name.slice(keyIndex + 1)];
422
+ let markClass = action.markClass ? " " + action.markClass : "";
385
423
  return elt("button", {
386
424
  type: "button",
387
- class: "cm-diagnosticAction",
425
+ class: "cm-diagnosticAction" + markClass,
388
426
  onclick: click,
389
427
  onmousedown: click,
390
428
  "aria-label": ` Action: ${name}${keyIndex < 0 ? "" : ` (access key "${keys[i]})"`}.`
@@ -807,7 +845,7 @@ const lintGutterTooltip = /*@__PURE__*/StateField.define({
807
845
  create() { return null; },
808
846
  update(tooltip, tr) {
809
847
  if (tooltip && tr.docChanged)
810
- tooltip = hideTooltip(tr, tooltip) ? null : Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
848
+ tooltip = hideTooltip(tr, tooltip) ? null : { ...tooltip, pos: tr.changes.mapPos(tooltip.pos) };
811
849
  return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
812
850
  },
813
851
  provide: field => showTooltip.from(field)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/lint",
3
- "version": "6.8.5",
3
+ "version": "6.9.1",
4
4
  "description": "Linting support for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",