@codemirror/lint 6.4.2 → 6.6.0

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.6.0 (2024-04-29)
2
+
3
+ ### New features
4
+
5
+ The new `hideOn` configuration option can be used to control in what circumstances lint tooltips get hidden by state changes.
6
+
7
+ ## 6.5.0 (2024-01-30)
8
+
9
+ ### Bug fixes
10
+
11
+ Make lint mark decorations inclusive, so that they are applied even if the marked content is replaced by a widget decoration.
12
+
13
+ ### New features
14
+
15
+ `linter` can now be called with null as source to only provide a configuration.
16
+
17
+ `markerFilter` and `tooltipFilter` function now get passed the current editor state.
18
+
1
19
  ## 6.4.2 (2023-09-14)
2
20
 
3
21
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -22,7 +22,7 @@ class LintState {
22
22
  let markedDiagnostics = diagnostics;
23
23
  let diagnosticFilter = state.facet(lintConfig).markerFilter;
24
24
  if (diagnosticFilter)
25
- markedDiagnostics = diagnosticFilter(markedDiagnostics);
25
+ markedDiagnostics = diagnosticFilter(markedDiagnostics, state);
26
26
  let ranges = view.Decoration.set(markedDiagnostics.map((d) => {
27
27
  // For zero-length ranges or ranges covering only a line break, create a widget
28
28
  return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
@@ -32,7 +32,8 @@ class LintState {
32
32
  }).range(d.from)
33
33
  : view.Decoration.mark({
34
34
  attributes: { class: "cm-lintRange cm-lintRange-" + d.severity + (d.markClass ? " " + d.markClass : "") },
35
- diagnostic: d
35
+ diagnostic: d,
36
+ inclusive: true
36
37
  }).range(d.from, d.to);
37
38
  }), true);
38
39
  return new LintState(ranges, panel, findDiagnostic(ranges));
@@ -49,8 +50,12 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
49
50
  return found;
50
51
  }
51
52
  function hideTooltip(tr, tooltip) {
53
+ let from = tooltip.pos, to = tooltip.end || from;
54
+ let result = tr.state.facet(lintConfig).hideOn(tr, from, to);
55
+ if (result != null)
56
+ return result;
52
57
  let line = tr.startState.doc.lineAt(tooltip.pos);
53
- return !!(tr.effects.some(e => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(line.from, line.to));
58
+ return !!(tr.effects.some(e => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(line.from, Math.max(line.to, to)));
54
59
  }
55
60
  function maybeEnableLint(state$1, effects) {
56
61
  return state$1.field(lintState, false) ? effects : effects.concat(state.StateEffect.appendConfig.of(lintExtensions));
@@ -108,7 +113,7 @@ function diagnosticCount(state) {
108
113
  let lint = state.field(lintState, false);
109
114
  return lint ? lint.diagnostics.size : 0;
110
115
  }
111
- const activeMark = view.Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
116
+ const activeMark = view.Decoration.mark({ class: "cm-lintRange cm-lintRange-active", inclusive: true });
112
117
  function lintTooltip(view, pos, side) {
113
118
  let { diagnostics } = view.state.field(lintState);
114
119
  let found = [], stackStart = 2e8, stackEnd = 0;
@@ -122,7 +127,7 @@ function lintTooltip(view, pos, side) {
122
127
  });
123
128
  let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
124
129
  if (diagnosticFilter)
125
- found = diagnosticFilter(found);
130
+ found = diagnosticFilter(found, view.state);
126
131
  if (!found.length)
127
132
  return null;
128
133
  return {
@@ -227,11 +232,12 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
227
232
  else {
228
233
  this.set = false;
229
234
  let { state } = this.view, { sources } = state.facet(lintConfig);
230
- Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
231
- let all = annotations.reduce((a, b) => a.concat(b));
232
- if (this.view.state.doc == state.doc)
233
- this.view.dispatch(setDiagnostics(this.view.state, all));
234
- }, error => { view.logException(this.view.state, error); });
235
+ if (sources.length)
236
+ Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
237
+ let all = annotations.reduce((a, b) => a.concat(b));
238
+ if (this.view.state.doc == state.doc)
239
+ this.view.dispatch(setDiagnostics(this.view.state, all));
240
+ }, error => { view.logException(this.view.state, error); });
235
241
  }
236
242
  }
237
243
  update(update) {
@@ -257,11 +263,12 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
257
263
  });
258
264
  const lintConfig = state.Facet.define({
259
265
  combine(input) {
260
- return Object.assign({ sources: input.map(i => i.source) }, state.combineConfig(input.map(i => i.config), {
266
+ return Object.assign({ sources: input.map(i => i.source).filter(x => x != null) }, state.combineConfig(input.map(i => i.config), {
261
267
  delay: 750,
262
268
  markerFilter: null,
263
269
  tooltipFilter: null,
264
- needsRefresh: null
270
+ needsRefresh: null,
271
+ hideOn: () => null,
265
272
  }, {
266
273
  needsRefresh: (a, b) => !a ? b : !b ? a : u => a(u) || b(u)
267
274
  }));
@@ -270,7 +277,8 @@ const lintConfig = state.Facet.define({
270
277
  /**
271
278
  Given a diagnostic source, this function returns an extension that
272
279
  enables linting with that source. It will be called whenever the
273
- editor is idle (after its content changed).
280
+ editor is idle (after its content changed). If `null` is given as
281
+ source, this only configures the lint extension.
274
282
  */
275
283
  function linter(source, config = {}) {
276
284
  return [
@@ -628,7 +636,7 @@ class LintGutterMarker extends view.GutterMarker {
628
636
  let diagnostics = this.diagnostics;
629
637
  let diagnosticsFilter = view.state.facet(lintGutterConfig).tooltipFilter;
630
638
  if (diagnosticsFilter)
631
- diagnostics = diagnosticsFilter(diagnostics);
639
+ diagnostics = diagnosticsFilter(diagnostics, view.state);
632
640
  if (diagnostics.length)
633
641
  elt.onmouseover = () => gutterMarkerMouseOver(view, elt, diagnostics);
634
642
  return elt;
@@ -707,7 +715,7 @@ const lintGutterMarkers = state.StateField.define({
707
715
  if (effect.is(setDiagnosticsEffect)) {
708
716
  let diagnostics = effect.value;
709
717
  if (diagnosticFilter)
710
- diagnostics = diagnosticFilter(diagnostics || []);
718
+ diagnostics = diagnosticFilter(diagnostics || [], tr.state);
711
719
  markers = markersForDiagnostics(tr.state.doc, diagnostics.slice(0));
712
720
  }
713
721
  }
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _codemirror_state from '@codemirror/state';
2
- import { EditorState, TransactionSpec, Extension } from '@codemirror/state';
2
+ import { EditorState, TransactionSpec, Extension, Transaction } from '@codemirror/state';
3
3
  import { EditorView, Command, KeyBinding, ViewUpdate } from '@codemirror/view';
4
4
 
5
5
  type Severity = "hint" | "info" | "warning" | "error";
@@ -62,7 +62,7 @@ interface Action {
62
62
  */
63
63
  apply: (view: EditorView, from: number, to: number) => void;
64
64
  }
65
- type DiagnosticFilter = (diagnostics: readonly Diagnostic[]) => Diagnostic[];
65
+ type DiagnosticFilter = (diagnostics: readonly Diagnostic[], state: EditorState) => Diagnostic[];
66
66
  interface LintConfig {
67
67
  /**
68
68
  Time to wait (in milliseconds) after a change before running
@@ -85,6 +85,14 @@ interface LintConfig {
85
85
  tooltip will appear if the empty set is returned.
86
86
  */
87
87
  tooltipFilter?: null | DiagnosticFilter;
88
+ /**
89
+ Can be used to control what kind of transactions cause lint
90
+ hover tooltips associated with the given document range to be
91
+ hidden. By default any transactions that changes the line
92
+ around the range will hide it. Returning null falls back to this
93
+ behavior.
94
+ */
95
+ hideOn?: (tr: Transaction, from: number, to: number) => boolean | null;
88
96
  }
89
97
  interface LintGutterConfig {
90
98
  /**
@@ -147,9 +155,10 @@ type LintSource = (view: EditorView) => readonly Diagnostic[] | Promise<readonly
147
155
  /**
148
156
  Given a diagnostic source, this function returns an extension that
149
157
  enables linting with that source. It will be called whenever the
150
- editor is idle (after its content changed).
158
+ editor is idle (after its content changed). If `null` is given as
159
+ source, this only configures the lint extension.
151
160
  */
152
- declare function linter(source: LintSource, config?: LintConfig): Extension;
161
+ declare function linter(source: LintSource | null, config?: LintConfig): Extension;
153
162
  /**
154
163
  Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter) to run when the
155
164
  editor is idle to run right away.
@@ -170,4 +179,4 @@ arguments hold the diagnostic's current position.
170
179
  */
171
180
  declare function forEachDiagnostic(state: EditorState, f: (d: Diagnostic, from: number, to: number) => void): void;
172
181
 
173
- export { Action, Diagnostic, LintSource, closeLintPanel, diagnosticCount, forEachDiagnostic, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, previousDiagnostic, setDiagnostics, setDiagnosticsEffect };
182
+ export { type Action, type Diagnostic, type LintSource, closeLintPanel, diagnosticCount, forEachDiagnostic, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, previousDiagnostic, setDiagnostics, setDiagnosticsEffect };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _codemirror_state from '@codemirror/state';
2
- import { EditorState, TransactionSpec, Extension } from '@codemirror/state';
2
+ import { EditorState, TransactionSpec, Extension, Transaction } from '@codemirror/state';
3
3
  import { EditorView, Command, KeyBinding, ViewUpdate } from '@codemirror/view';
4
4
 
5
5
  type Severity = "hint" | "info" | "warning" | "error";
@@ -62,7 +62,7 @@ interface Action {
62
62
  */
63
63
  apply: (view: EditorView, from: number, to: number) => void;
64
64
  }
65
- type DiagnosticFilter = (diagnostics: readonly Diagnostic[]) => Diagnostic[];
65
+ type DiagnosticFilter = (diagnostics: readonly Diagnostic[], state: EditorState) => Diagnostic[];
66
66
  interface LintConfig {
67
67
  /**
68
68
  Time to wait (in milliseconds) after a change before running
@@ -85,6 +85,14 @@ interface LintConfig {
85
85
  tooltip will appear if the empty set is returned.
86
86
  */
87
87
  tooltipFilter?: null | DiagnosticFilter;
88
+ /**
89
+ Can be used to control what kind of transactions cause lint
90
+ hover tooltips associated with the given document range to be
91
+ hidden. By default any transactions that changes the line
92
+ around the range will hide it. Returning null falls back to this
93
+ behavior.
94
+ */
95
+ hideOn?: (tr: Transaction, from: number, to: number) => boolean | null;
88
96
  }
89
97
  interface LintGutterConfig {
90
98
  /**
@@ -147,9 +155,10 @@ type LintSource = (view: EditorView) => readonly Diagnostic[] | Promise<readonly
147
155
  /**
148
156
  Given a diagnostic source, this function returns an extension that
149
157
  enables linting with that source. It will be called whenever the
150
- editor is idle (after its content changed).
158
+ editor is idle (after its content changed). If `null` is given as
159
+ source, this only configures the lint extension.
151
160
  */
152
- declare function linter(source: LintSource, config?: LintConfig): Extension;
161
+ declare function linter(source: LintSource | null, config?: LintConfig): Extension;
153
162
  /**
154
163
  Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter) to run when the
155
164
  editor is idle to run right away.
@@ -170,4 +179,4 @@ arguments hold the diagnostic's current position.
170
179
  */
171
180
  declare function forEachDiagnostic(state: EditorState, f: (d: Diagnostic, from: number, to: number) => void): void;
172
181
 
173
- export { Action, Diagnostic, LintSource, closeLintPanel, diagnosticCount, forEachDiagnostic, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, previousDiagnostic, setDiagnostics, setDiagnosticsEffect };
182
+ export { type Action, type Diagnostic, type LintSource, closeLintPanel, diagnosticCount, forEachDiagnostic, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, previousDiagnostic, setDiagnostics, setDiagnosticsEffect };
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ class LintState {
20
20
  let markedDiagnostics = diagnostics;
21
21
  let diagnosticFilter = state.facet(lintConfig).markerFilter;
22
22
  if (diagnosticFilter)
23
- markedDiagnostics = diagnosticFilter(markedDiagnostics);
23
+ markedDiagnostics = diagnosticFilter(markedDiagnostics, state);
24
24
  let ranges = Decoration.set(markedDiagnostics.map((d) => {
25
25
  // For zero-length ranges or ranges covering only a line break, create a widget
26
26
  return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
@@ -30,7 +30,8 @@ class LintState {
30
30
  }).range(d.from)
31
31
  : Decoration.mark({
32
32
  attributes: { class: "cm-lintRange cm-lintRange-" + d.severity + (d.markClass ? " " + d.markClass : "") },
33
- diagnostic: d
33
+ diagnostic: d,
34
+ inclusive: true
34
35
  }).range(d.from, d.to);
35
36
  }), true);
36
37
  return new LintState(ranges, panel, findDiagnostic(ranges));
@@ -47,8 +48,12 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
47
48
  return found;
48
49
  }
49
50
  function hideTooltip(tr, tooltip) {
51
+ let from = tooltip.pos, to = tooltip.end || from;
52
+ let result = tr.state.facet(lintConfig).hideOn(tr, from, to);
53
+ if (result != null)
54
+ return result;
50
55
  let line = tr.startState.doc.lineAt(tooltip.pos);
51
- return !!(tr.effects.some(e => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(line.from, line.to));
56
+ return !!(tr.effects.some(e => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(line.from, Math.max(line.to, to)));
52
57
  }
53
58
  function maybeEnableLint(state, effects) {
54
59
  return state.field(lintState, false) ? effects : effects.concat(StateEffect.appendConfig.of(lintExtensions));
@@ -106,7 +111,7 @@ function diagnosticCount(state) {
106
111
  let lint = state.field(lintState, false);
107
112
  return lint ? lint.diagnostics.size : 0;
108
113
  }
109
- const activeMark = /*@__PURE__*/Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
114
+ const activeMark = /*@__PURE__*/Decoration.mark({ class: "cm-lintRange cm-lintRange-active", inclusive: true });
110
115
  function lintTooltip(view, pos, side) {
111
116
  let { diagnostics } = view.state.field(lintState);
112
117
  let found = [], stackStart = 2e8, stackEnd = 0;
@@ -120,7 +125,7 @@ function lintTooltip(view, pos, side) {
120
125
  });
121
126
  let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
122
127
  if (diagnosticFilter)
123
- found = diagnosticFilter(found);
128
+ found = diagnosticFilter(found, view.state);
124
129
  if (!found.length)
125
130
  return null;
126
131
  return {
@@ -225,11 +230,12 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
225
230
  else {
226
231
  this.set = false;
227
232
  let { state } = this.view, { sources } = state.facet(lintConfig);
228
- Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
229
- let all = annotations.reduce((a, b) => a.concat(b));
230
- if (this.view.state.doc == state.doc)
231
- this.view.dispatch(setDiagnostics(this.view.state, all));
232
- }, error => { logException(this.view.state, error); });
233
+ if (sources.length)
234
+ Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
235
+ let all = annotations.reduce((a, b) => a.concat(b));
236
+ if (this.view.state.doc == state.doc)
237
+ this.view.dispatch(setDiagnostics(this.view.state, all));
238
+ }, error => { logException(this.view.state, error); });
233
239
  }
234
240
  }
235
241
  update(update) {
@@ -255,11 +261,12 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
255
261
  });
256
262
  const lintConfig = /*@__PURE__*/Facet.define({
257
263
  combine(input) {
258
- return Object.assign({ sources: input.map(i => i.source) }, combineConfig(input.map(i => i.config), {
264
+ return Object.assign({ sources: input.map(i => i.source).filter(x => x != null) }, combineConfig(input.map(i => i.config), {
259
265
  delay: 750,
260
266
  markerFilter: null,
261
267
  tooltipFilter: null,
262
- needsRefresh: null
268
+ needsRefresh: null,
269
+ hideOn: () => null,
263
270
  }, {
264
271
  needsRefresh: (a, b) => !a ? b : !b ? a : u => a(u) || b(u)
265
272
  }));
@@ -268,7 +275,8 @@ const lintConfig = /*@__PURE__*/Facet.define({
268
275
  /**
269
276
  Given a diagnostic source, this function returns an extension that
270
277
  enables linting with that source. It will be called whenever the
271
- editor is idle (after its content changed).
278
+ editor is idle (after its content changed). If `null` is given as
279
+ source, this only configures the lint extension.
272
280
  */
273
281
  function linter(source, config = {}) {
274
282
  return [
@@ -626,7 +634,7 @@ class LintGutterMarker extends GutterMarker {
626
634
  let diagnostics = this.diagnostics;
627
635
  let diagnosticsFilter = view.state.facet(lintGutterConfig).tooltipFilter;
628
636
  if (diagnosticsFilter)
629
- diagnostics = diagnosticsFilter(diagnostics);
637
+ diagnostics = diagnosticsFilter(diagnostics, view.state);
630
638
  if (diagnostics.length)
631
639
  elt.onmouseover = () => gutterMarkerMouseOver(view, elt, diagnostics);
632
640
  return elt;
@@ -705,7 +713,7 @@ const lintGutterMarkers = /*@__PURE__*/StateField.define({
705
713
  if (effect.is(setDiagnosticsEffect)) {
706
714
  let diagnostics = effect.value;
707
715
  if (diagnosticFilter)
708
- diagnostics = diagnosticFilter(diagnostics || []);
716
+ diagnostics = diagnosticFilter(diagnostics || [], tr.state);
709
717
  markers = markersForDiagnostics(tr.state.doc, diagnostics.slice(0));
710
718
  }
711
719
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/lint",
3
- "version": "6.4.2",
3
+ "version": "6.6.0",
4
4
  "description": "Linting support for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",