@codemirror/lint 0.20.0 → 0.20.3

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,23 @@
1
+ ## 0.20.3 (2022-05-25)
2
+
3
+ ### New features
4
+
5
+ Diagnostic objects may now have a `renderMessage` method to render their message to the DOM.
6
+
7
+ ## 0.20.2 (2022-05-02)
8
+
9
+ ### New features
10
+
11
+ The package now exports the `LintSource` function type.
12
+
13
+ The new `markerFilter` and `tooltipFilter` options to `linter` and `lintGutter` allow more control over which diagnostics are visible and which have tooltips.
14
+
15
+ ## 0.20.1 (2022-04-22)
16
+
17
+ ### Bug fixes
18
+
19
+ Hide lint tooltips when the document is changed.
20
+
1
21
  ## 0.20.0 (2022-04-20)
2
22
 
3
23
  ### Breaking changes
package/dist/index.cjs CHANGED
@@ -24,7 +24,12 @@ class LintState {
24
24
  this.selected = selected;
25
25
  }
26
26
  static init(diagnostics, panel, state) {
27
- let ranges = view.Decoration.set(diagnostics.map((d) => {
27
+ // Filter the list of diagnostics for which to create markers
28
+ let markedDiagnostics = diagnostics;
29
+ let diagnosticFilter = state.facet(lintConfig).markerFilter;
30
+ if (diagnosticFilter)
31
+ markedDiagnostics = diagnosticFilter(markedDiagnostics);
32
+ let ranges = view.Decoration.set(markedDiagnostics.map((d) => {
28
33
  // For zero-length ranges or ranges covering only a line break, create a widget
29
34
  return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
30
35
  ? view.Decoration.widget({
@@ -49,6 +54,9 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
49
54
  });
50
55
  return found;
51
56
  }
57
+ function hideTooltip(tr, tooltip) {
58
+ return !!(tr.effects.some(e => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(tooltip.pos));
59
+ }
52
60
  function maybeEnableLint(state$1, effects) {
53
61
  return state$1.field(lintState, false) ? effects : effects.concat(state.StateEffect.appendConfig.of([
54
62
  lintState,
@@ -58,7 +66,7 @@ function maybeEnableLint(state$1, effects) {
58
66
  activeMark.range(selected.from, selected.to)
59
67
  ]);
60
68
  }),
61
- view.hoverTooltip(lintTooltip),
69
+ view.hoverTooltip(lintTooltip, { hideOn: hideTooltip }),
62
70
  baseTheme
63
71
  ]));
64
72
  }
@@ -127,6 +135,9 @@ function lintTooltip(view, pos, side) {
127
135
  stackEnd = Math.max(to, stackEnd);
128
136
  }
129
137
  });
138
+ let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
139
+ if (diagnosticFilter)
140
+ found = diagnosticFilter(found);
130
141
  if (!found.length)
131
142
  return null;
132
143
  return {
@@ -194,7 +205,7 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
194
205
  this.view = view;
195
206
  this.timeout = -1;
196
207
  this.set = true;
197
- let { delay } = view.state.facet(lintSource);
208
+ let { delay } = view.state.facet(lintConfig);
198
209
  this.lintTime = Date.now() + delay;
199
210
  this.run = this.run.bind(this);
200
211
  this.timeout = setTimeout(this.run, delay);
@@ -206,7 +217,7 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
206
217
  }
207
218
  else {
208
219
  this.set = false;
209
- let { state } = this.view, { sources } = state.facet(lintSource);
220
+ let { state } = this.view, { sources } = state.facet(lintConfig);
210
221
  Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
211
222
  let all = annotations.reduce((a, b) => a.concat(b));
212
223
  if (this.view.state.doc == state.doc)
@@ -215,12 +226,12 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
215
226
  }
216
227
  }
217
228
  update(update) {
218
- let source = update.state.facet(lintSource);
219
- if (update.docChanged || source != update.startState.facet(lintSource)) {
220
- this.lintTime = Date.now() + source.delay;
229
+ let config = update.state.facet(lintConfig);
230
+ if (update.docChanged || config != update.startState.facet(lintConfig)) {
231
+ this.lintTime = Date.now() + config.delay;
221
232
  if (!this.set) {
222
233
  this.set = true;
223
- this.timeout = setTimeout(this.run, source.delay);
234
+ this.timeout = setTimeout(this.run, config.delay);
224
235
  }
225
236
  }
226
237
  }
@@ -234,9 +245,13 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
234
245
  clearTimeout(this.timeout);
235
246
  }
236
247
  });
237
- const lintSource = state.Facet.define({
248
+ const lintConfig = state.Facet.define({
238
249
  combine(input) {
239
- return { sources: input.map(i => i.source), delay: input.length ? Math.max(...input.map(i => i.delay)) : 750 };
250
+ return Object.assign({ sources: input.map(i => i.source) }, state.combineConfig(input.map(i => i.config), {
251
+ delay: 750,
252
+ markerFilter: null,
253
+ tooltipFilter: null
254
+ }));
240
255
  },
241
256
  enables: lintPlugin
242
257
  });
@@ -246,8 +261,7 @@ enables linting with that source. It will be called whenever the
246
261
  editor is idle (after its content changed).
247
262
  */
248
263
  function linter(source, config = {}) {
249
- var _a;
250
- return lintSource.of({ source, delay: (_a = config.delay) !== null && _a !== void 0 ? _a : 750 });
264
+ return lintConfig.of({ source, config });
251
265
  }
252
266
  /**
253
267
  Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter) to run when the
@@ -276,7 +290,7 @@ function assignKeys(actions) {
276
290
  function renderDiagnostic(view, diagnostic, inPanel) {
277
291
  var _a;
278
292
  let keys = inPanel ? assignKeys(diagnostic.actions) : [];
279
- return elt__default["default"]("li", { class: "cm-diagnostic cm-diagnostic-" + diagnostic.severity }, elt__default["default"]("span", { class: "cm-diagnosticText" }, diagnostic.message), (_a = diagnostic.actions) === null || _a === void 0 ? void 0 : _a.map((action, i) => {
293
+ return elt__default["default"]("li", { class: "cm-diagnostic cm-diagnostic-" + diagnostic.severity }, elt__default["default"]("span", { class: "cm-diagnosticText" }, diagnostic.renderMessage ? diagnostic.renderMessage() : diagnostic.message), (_a = diagnostic.actions) === null || _a === void 0 ? void 0 : _a.map((action, i) => {
280
294
  let click = (e) => {
281
295
  e.preventDefault();
282
296
  let found = findDiagnostic(view.state.field(lintState).diagnostics, diagnostic);
@@ -585,7 +599,12 @@ class LintGutterMarker extends view.GutterMarker {
585
599
  toDOM(view) {
586
600
  let elt = document.createElement("div");
587
601
  elt.className = "cm-lint-marker cm-lint-marker-" + this.severity;
588
- elt.onmouseover = () => gutterMarkerMouseOver(view, elt, this.diagnostics);
602
+ let diagnostics = this.diagnostics;
603
+ let diagnosticsFilter = view.state.facet(lintGutterConfig).tooltipFilter;
604
+ if (diagnosticsFilter)
605
+ diagnostics = diagnosticsFilter(diagnostics);
606
+ if (diagnostics.length)
607
+ elt.onmouseover = () => gutterMarkerMouseOver(view, elt, diagnostics);
589
608
  return elt;
590
609
  }
591
610
  }
@@ -657,10 +676,15 @@ const lintGutterMarkers = state.StateField.define({
657
676
  },
658
677
  update(markers, tr) {
659
678
  markers = markers.map(tr.changes);
660
- for (let effect of tr.effects)
679
+ let diagnosticFilter = tr.state.facet(lintGutterConfig).markerFilter;
680
+ for (let effect of tr.effects) {
661
681
  if (effect.is(setDiagnosticsEffect)) {
662
- markers = markersForDiagnostics(tr.state.doc, effect.value);
682
+ let diagnostics = effect.value;
683
+ if (diagnosticFilter)
684
+ diagnostics = diagnosticFilter(diagnostics || []);
685
+ markers = markersForDiagnostics(tr.state.doc, diagnostics.slice(0));
663
686
  }
687
+ }
664
688
  return markers;
665
689
  }
666
690
  });
@@ -669,7 +693,7 @@ const lintGutterTooltip = state.StateField.define({
669
693
  create() { return null; },
670
694
  update(tooltip, tr) {
671
695
  if (tooltip && tr.docChanged)
672
- tooltip = Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
696
+ tooltip = hideTooltip(tr, tooltip) ? null : Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
673
697
  return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
674
698
  },
675
699
  provide: field => view.showTooltip.from(field)
@@ -699,6 +723,8 @@ const lintGutterConfig = state.Facet.define({
699
723
  combine(configs) {
700
724
  return state.combineConfig(configs, {
701
725
  hoverTime: 300 /* Time */,
726
+ markerFilter: null,
727
+ tooltipFilter: null
702
728
  });
703
729
  }
704
730
  });
package/dist/index.d.ts CHANGED
@@ -31,6 +31,11 @@ interface Diagnostic {
31
31
  */
32
32
  message: string;
33
33
  /**
34
+ An optional custom rendering function that displays the message
35
+ as a DOM node.
36
+ */
37
+ renderMessage?: () => Node;
38
+ /**
34
39
  An optional array of actions that can be taken on this
35
40
  diagnostic.
36
41
  */
@@ -51,11 +56,39 @@ interface Action {
51
56
  */
52
57
  apply: (view: EditorView, from: number, to: number) => void;
53
58
  }
59
+ declare type DiagnosticFilter = (diagnostics: readonly Diagnostic[]) => Diagnostic[];
60
+ interface LintConfig {
61
+ /**
62
+ Time to wait (in milliseconds) after a change before running
63
+ the linter. Defaults to 750ms.
64
+ */
65
+ delay?: number;
66
+ /**
67
+ Optional filter to determine which diagnostics produce markers
68
+ in the content.
69
+ */
70
+ markerFilter?: null | DiagnosticFilter;
71
+ /**
72
+ Filter applied to a set of diagnostics shown in a tooltip. No
73
+ tooltip will appear if the empty set is returned.
74
+ */
75
+ tooltipFilter?: null | DiagnosticFilter;
76
+ }
54
77
  interface LintGutterConfig {
55
78
  /**
56
79
  The delay before showing a tooltip when hovering over a lint gutter marker.
57
80
  */
58
81
  hoverTime?: number;
82
+ /**
83
+ Optional filter determining which diagnostics show a marker in
84
+ the gutter.
85
+ */
86
+ markerFilter?: null | DiagnosticFilter;
87
+ /**
88
+ Optional filter for diagnostics displayed in a tooltip, which
89
+ can also be used to prevent a tooltip appearing.
90
+ */
91
+ tooltipFilter?: null | DiagnosticFilter;
59
92
  }
60
93
  /**
61
94
  Returns a transaction spec which updates the current set of
@@ -91,19 +124,16 @@ A set of default key bindings for the lint functionality.
91
124
  - F8: [`nextDiagnostic`](https://codemirror.net/6/docs/ref/#lint.nextDiagnostic)
92
125
  */
93
126
  declare const lintKeymap: readonly KeyBinding[];
127
+ /**
128
+ The type of a function that produces diagnostics.
129
+ */
94
130
  declare type LintSource = (view: EditorView) => readonly Diagnostic[] | Promise<readonly Diagnostic[]>;
95
131
  /**
96
132
  Given a diagnostic source, this function returns an extension that
97
133
  enables linting with that source. It will be called whenever the
98
134
  editor is idle (after its content changed).
99
135
  */
100
- declare function linter(source: LintSource, config?: {
101
- /**
102
- Time to wait (in milliseconds) after a change before running
103
- the linter. Defaults to 750ms.
104
- */
105
- delay?: number;
106
- }): Extension;
136
+ declare function linter(source: LintSource, config?: LintConfig): Extension;
107
137
  /**
108
138
  Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter) to run when the
109
139
  editor is idle to run right away.
@@ -116,4 +146,4 @@ the diagnostics.
116
146
  */
117
147
  declare function lintGutter(config?: LintGutterConfig): Extension;
118
148
 
119
- export { Action, Diagnostic, closeLintPanel, diagnosticCount, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };
149
+ export { Action, Diagnostic, LintSource, closeLintPanel, diagnosticCount, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Decoration, showPanel, EditorView, ViewPlugin, hoverTooltip, logException, gutter, showTooltip, getPanel, WidgetType, GutterMarker } from '@codemirror/view';
2
- import { StateEffect, StateField, Facet, RangeSet, combineConfig } from '@codemirror/state';
2
+ import { StateEffect, StateField, Facet, combineConfig, RangeSet } from '@codemirror/state';
3
3
  import elt from 'crelt';
4
4
 
5
5
  class SelectedDiagnostic {
@@ -16,7 +16,12 @@ class LintState {
16
16
  this.selected = selected;
17
17
  }
18
18
  static init(diagnostics, panel, state) {
19
- let ranges = Decoration.set(diagnostics.map((d) => {
19
+ // Filter the list of diagnostics for which to create markers
20
+ let markedDiagnostics = diagnostics;
21
+ let diagnosticFilter = state.facet(lintConfig).markerFilter;
22
+ if (diagnosticFilter)
23
+ markedDiagnostics = diagnosticFilter(markedDiagnostics);
24
+ let ranges = Decoration.set(markedDiagnostics.map((d) => {
20
25
  // For zero-length ranges or ranges covering only a line break, create a widget
21
26
  return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
22
27
  ? Decoration.widget({
@@ -41,6 +46,9 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
41
46
  });
42
47
  return found;
43
48
  }
49
+ function hideTooltip(tr, tooltip) {
50
+ return !!(tr.effects.some(e => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(tooltip.pos));
51
+ }
44
52
  function maybeEnableLint(state, effects) {
45
53
  return state.field(lintState, false) ? effects : effects.concat(StateEffect.appendConfig.of([
46
54
  lintState,
@@ -50,7 +58,7 @@ function maybeEnableLint(state, effects) {
50
58
  activeMark.range(selected.from, selected.to)
51
59
  ]);
52
60
  }),
53
- hoverTooltip(lintTooltip),
61
+ hoverTooltip(lintTooltip, { hideOn: hideTooltip }),
54
62
  baseTheme
55
63
  ]));
56
64
  }
@@ -119,6 +127,9 @@ function lintTooltip(view, pos, side) {
119
127
  stackEnd = Math.max(to, stackEnd);
120
128
  }
121
129
  });
130
+ let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
131
+ if (diagnosticFilter)
132
+ found = diagnosticFilter(found);
122
133
  if (!found.length)
123
134
  return null;
124
135
  return {
@@ -186,7 +197,7 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
186
197
  this.view = view;
187
198
  this.timeout = -1;
188
199
  this.set = true;
189
- let { delay } = view.state.facet(lintSource);
200
+ let { delay } = view.state.facet(lintConfig);
190
201
  this.lintTime = Date.now() + delay;
191
202
  this.run = this.run.bind(this);
192
203
  this.timeout = setTimeout(this.run, delay);
@@ -198,7 +209,7 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
198
209
  }
199
210
  else {
200
211
  this.set = false;
201
- let { state } = this.view, { sources } = state.facet(lintSource);
212
+ let { state } = this.view, { sources } = state.facet(lintConfig);
202
213
  Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
203
214
  let all = annotations.reduce((a, b) => a.concat(b));
204
215
  if (this.view.state.doc == state.doc)
@@ -207,12 +218,12 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
207
218
  }
208
219
  }
209
220
  update(update) {
210
- let source = update.state.facet(lintSource);
211
- if (update.docChanged || source != update.startState.facet(lintSource)) {
212
- this.lintTime = Date.now() + source.delay;
221
+ let config = update.state.facet(lintConfig);
222
+ if (update.docChanged || config != update.startState.facet(lintConfig)) {
223
+ this.lintTime = Date.now() + config.delay;
213
224
  if (!this.set) {
214
225
  this.set = true;
215
- this.timeout = setTimeout(this.run, source.delay);
226
+ this.timeout = setTimeout(this.run, config.delay);
216
227
  }
217
228
  }
218
229
  }
@@ -226,9 +237,13 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
226
237
  clearTimeout(this.timeout);
227
238
  }
228
239
  });
229
- const lintSource = /*@__PURE__*/Facet.define({
240
+ const lintConfig = /*@__PURE__*/Facet.define({
230
241
  combine(input) {
231
- return { sources: input.map(i => i.source), delay: input.length ? Math.max(...input.map(i => i.delay)) : 750 };
242
+ return Object.assign({ sources: input.map(i => i.source) }, combineConfig(input.map(i => i.config), {
243
+ delay: 750,
244
+ markerFilter: null,
245
+ tooltipFilter: null
246
+ }));
232
247
  },
233
248
  enables: lintPlugin
234
249
  });
@@ -238,8 +253,7 @@ enables linting with that source. It will be called whenever the
238
253
  editor is idle (after its content changed).
239
254
  */
240
255
  function linter(source, config = {}) {
241
- var _a;
242
- return lintSource.of({ source, delay: (_a = config.delay) !== null && _a !== void 0 ? _a : 750 });
256
+ return lintConfig.of({ source, config });
243
257
  }
244
258
  /**
245
259
  Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter) to run when the
@@ -268,7 +282,7 @@ function assignKeys(actions) {
268
282
  function renderDiagnostic(view, diagnostic, inPanel) {
269
283
  var _a;
270
284
  let keys = inPanel ? assignKeys(diagnostic.actions) : [];
271
- return elt("li", { class: "cm-diagnostic cm-diagnostic-" + diagnostic.severity }, elt("span", { class: "cm-diagnosticText" }, diagnostic.message), (_a = diagnostic.actions) === null || _a === void 0 ? void 0 : _a.map((action, i) => {
285
+ return elt("li", { class: "cm-diagnostic cm-diagnostic-" + diagnostic.severity }, elt("span", { class: "cm-diagnosticText" }, diagnostic.renderMessage ? diagnostic.renderMessage() : diagnostic.message), (_a = diagnostic.actions) === null || _a === void 0 ? void 0 : _a.map((action, i) => {
272
286
  let click = (e) => {
273
287
  e.preventDefault();
274
288
  let found = findDiagnostic(view.state.field(lintState).diagnostics, diagnostic);
@@ -577,7 +591,12 @@ class LintGutterMarker extends GutterMarker {
577
591
  toDOM(view) {
578
592
  let elt = document.createElement("div");
579
593
  elt.className = "cm-lint-marker cm-lint-marker-" + this.severity;
580
- elt.onmouseover = () => gutterMarkerMouseOver(view, elt, this.diagnostics);
594
+ let diagnostics = this.diagnostics;
595
+ let diagnosticsFilter = view.state.facet(lintGutterConfig).tooltipFilter;
596
+ if (diagnosticsFilter)
597
+ diagnostics = diagnosticsFilter(diagnostics);
598
+ if (diagnostics.length)
599
+ elt.onmouseover = () => gutterMarkerMouseOver(view, elt, diagnostics);
581
600
  return elt;
582
601
  }
583
602
  }
@@ -649,10 +668,15 @@ const lintGutterMarkers = /*@__PURE__*/StateField.define({
649
668
  },
650
669
  update(markers, tr) {
651
670
  markers = markers.map(tr.changes);
652
- for (let effect of tr.effects)
671
+ let diagnosticFilter = tr.state.facet(lintGutterConfig).markerFilter;
672
+ for (let effect of tr.effects) {
653
673
  if (effect.is(setDiagnosticsEffect)) {
654
- markers = markersForDiagnostics(tr.state.doc, effect.value);
674
+ let diagnostics = effect.value;
675
+ if (diagnosticFilter)
676
+ diagnostics = diagnosticFilter(diagnostics || []);
677
+ markers = markersForDiagnostics(tr.state.doc, diagnostics.slice(0));
655
678
  }
679
+ }
656
680
  return markers;
657
681
  }
658
682
  });
@@ -661,7 +685,7 @@ const lintGutterTooltip = /*@__PURE__*/StateField.define({
661
685
  create() { return null; },
662
686
  update(tooltip, tr) {
663
687
  if (tooltip && tr.docChanged)
664
- tooltip = Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
688
+ tooltip = hideTooltip(tr, tooltip) ? null : Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
665
689
  return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
666
690
  },
667
691
  provide: field => showTooltip.from(field)
@@ -691,6 +715,8 @@ const lintGutterConfig = /*@__PURE__*/Facet.define({
691
715
  combine(configs) {
692
716
  return combineConfig(configs, {
693
717
  hoverTime: 300 /* Time */,
718
+ markerFilter: null,
719
+ tooltipFilter: null
694
720
  });
695
721
  }
696
722
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/lint",
3
- "version": "0.20.0",
3
+ "version": "0.20.3",
4
4
  "description": "Linting support 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/state": "^0.20.0",
30
- "@codemirror/view": "^0.20.0",
30
+ "@codemirror/view": "^0.20.2",
31
31
  "crelt": "^1.0.5"
32
32
  },
33
33
  "devDependencies": {