@codemirror/lint 0.19.2 → 0.19.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 +24 -0
- package/dist/index.cjs +169 -23
- package/dist/index.d.ts +22 -3
- package/dist/index.js +163 -19
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## 0.19.5 (2022-02-25)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Make sure the lint gutter tooltips are positioned under their icon, even when the line is wrapped.
|
|
6
|
+
|
|
7
|
+
## 0.19.4 (2022-02-25)
|
|
8
|
+
|
|
9
|
+
### Bug fixes
|
|
10
|
+
|
|
11
|
+
Fix an issue where an outdated marker could stick around on the lint gutter after all diagnostics were removed.
|
|
12
|
+
|
|
13
|
+
### New features
|
|
14
|
+
|
|
15
|
+
Add a `hoverTime` option to the lint gutter. Change default hover time to 300
|
|
16
|
+
|
|
17
|
+
## 0.19.3 (2021-11-09)
|
|
18
|
+
|
|
19
|
+
### New features
|
|
20
|
+
|
|
21
|
+
Export a function `lintGutter` which returns an extension that installs a gutter marking lines with diagnostics.
|
|
22
|
+
|
|
23
|
+
The package now exports the effect used to update the diagnostics (`setDiagnosticsEffect`).
|
|
24
|
+
|
|
1
25
|
## 0.19.2 (2021-09-29)
|
|
2
26
|
|
|
3
27
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,8 @@ var view = require('@codemirror/view');
|
|
|
6
6
|
var state = require('@codemirror/state');
|
|
7
7
|
var tooltip = require('@codemirror/tooltip');
|
|
8
8
|
var panel = require('@codemirror/panel');
|
|
9
|
+
var gutter = require('@codemirror/gutter');
|
|
10
|
+
var rangeset = require('@codemirror/rangeset');
|
|
9
11
|
var elt = require('crelt');
|
|
10
12
|
|
|
11
13
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -51,9 +53,9 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
|
|
|
51
53
|
});
|
|
52
54
|
return found;
|
|
53
55
|
}
|
|
54
|
-
function maybeEnableLint(state$1, effects
|
|
56
|
+
function maybeEnableLint(state$1, effects) {
|
|
55
57
|
return state$1.field(lintState, false) ? effects : effects.concat(state.StateEffect.appendConfig.of([
|
|
56
|
-
lintState
|
|
58
|
+
lintState,
|
|
57
59
|
view.EditorView.decorations.compute([lintState], state => {
|
|
58
60
|
let { selected, panel } = state.field(lintState);
|
|
59
61
|
return !selected || !panel || selected.from == selected.to ? view.Decoration.none : view.Decoration.set([
|
|
@@ -66,13 +68,18 @@ function maybeEnableLint(state$1, effects, getState) {
|
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
70
|
Returns a transaction spec which updates the current set of
|
|
69
|
-
diagnostics
|
|
71
|
+
diagnostics, and enables the lint extension if if wasn't already
|
|
72
|
+
active.
|
|
70
73
|
*/
|
|
71
74
|
function setDiagnostics(state, diagnostics) {
|
|
72
75
|
return {
|
|
73
|
-
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)]
|
|
76
|
+
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)])
|
|
74
77
|
};
|
|
75
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
The state effect that updates the set of active diagnostics. Can
|
|
81
|
+
be useful when writing an extension that needs to track these.
|
|
82
|
+
*/
|
|
76
83
|
const setDiagnosticsEffect = state.StateEffect.define();
|
|
77
84
|
const togglePanel = state.StateEffect.define();
|
|
78
85
|
const movePanelSelection = state.StateEffect.define();
|
|
@@ -131,17 +138,20 @@ function lintTooltip(view, pos, side) {
|
|
|
131
138
|
end: stackEnd,
|
|
132
139
|
above: view.state.doc.lineAt(stackStart).to < stackEnd,
|
|
133
140
|
create() {
|
|
134
|
-
return { dom:
|
|
141
|
+
return { dom: diagnosticsTooltip(view, found) };
|
|
135
142
|
}
|
|
136
143
|
};
|
|
137
144
|
}
|
|
145
|
+
function diagnosticsTooltip(view, diagnostics) {
|
|
146
|
+
return elt__default["default"]("ul", { class: "cm-tooltip-lint" }, diagnostics.map(d => renderDiagnostic(view, d, false)));
|
|
147
|
+
}
|
|
138
148
|
/**
|
|
139
149
|
Command to open and focus the lint panel.
|
|
140
150
|
*/
|
|
141
151
|
const openLintPanel = (view) => {
|
|
142
152
|
let field = view.state.field(lintState, false);
|
|
143
153
|
if (!field || !field.panel)
|
|
144
|
-
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]
|
|
154
|
+
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]) });
|
|
145
155
|
let panel$1 = panel.getPanel(view, LintPanel.open);
|
|
146
156
|
if (panel$1)
|
|
147
157
|
panel$1.dom.querySelector(".cm-panel-lint ul").focus();
|
|
@@ -202,10 +212,8 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
|
|
|
202
212
|
this.set = false;
|
|
203
213
|
let { state } = this.view, { sources } = state.facet(lintSource);
|
|
204
214
|
Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
|
|
205
|
-
var _a, _b;
|
|
206
215
|
let all = annotations.reduce((a, b) => a.concat(b));
|
|
207
|
-
if (this.view.state.doc == state.doc
|
|
208
|
-
(all.length || ((_b = (_a = this.view.state.field(lintState, false)) === null || _a === void 0 ? void 0 : _a.diagnostics) === null || _b === void 0 ? void 0 : _b.size)))
|
|
216
|
+
if (this.view.state.doc == state.doc)
|
|
209
217
|
this.view.dispatch(setDiagnostics(this.view.state, all));
|
|
210
218
|
}, error => { view.logException(this.view.state, error); });
|
|
211
219
|
}
|
|
@@ -272,7 +280,7 @@ function assignKeys(actions) {
|
|
|
272
280
|
function renderDiagnostic(view, diagnostic, inPanel) {
|
|
273
281
|
var _a;
|
|
274
282
|
let keys = inPanel ? assignKeys(diagnostic.actions) : [];
|
|
275
|
-
return elt__default[
|
|
283
|
+
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) => {
|
|
276
284
|
let click = (e) => {
|
|
277
285
|
e.preventDefault();
|
|
278
286
|
let found = findDiagnostic(view.state.field(lintState).diagnostics, diagnostic);
|
|
@@ -281,16 +289,16 @@ function renderDiagnostic(view, diagnostic, inPanel) {
|
|
|
281
289
|
};
|
|
282
290
|
let { name } = action, keyIndex = keys[i] ? name.indexOf(keys[i]) : -1;
|
|
283
291
|
let nameElt = keyIndex < 0 ? name : [name.slice(0, keyIndex),
|
|
284
|
-
elt__default[
|
|
292
|
+
elt__default["default"]("u", name.slice(keyIndex, keyIndex + 1)),
|
|
285
293
|
name.slice(keyIndex + 1)];
|
|
286
|
-
return elt__default[
|
|
294
|
+
return elt__default["default"]("button", {
|
|
287
295
|
type: "button",
|
|
288
296
|
class: "cm-diagnosticAction",
|
|
289
297
|
onclick: click,
|
|
290
298
|
onmousedown: click,
|
|
291
299
|
"aria-label": ` Action: ${name}${keyIndex < 0 ? "" : ` (access key "${keys[i]})"`}.`
|
|
292
300
|
}, nameElt);
|
|
293
|
-
}), diagnostic.source && elt__default[
|
|
301
|
+
}), diagnostic.source && elt__default["default"]("div", { class: "cm-diagnosticSource" }, diagnostic.source));
|
|
294
302
|
}
|
|
295
303
|
class DiagnosticWidget extends view.WidgetType {
|
|
296
304
|
constructor(diagnostic) {
|
|
@@ -299,7 +307,7 @@ class DiagnosticWidget extends view.WidgetType {
|
|
|
299
307
|
}
|
|
300
308
|
eq(other) { return other.diagnostic == this.diagnostic; }
|
|
301
309
|
toDOM() {
|
|
302
|
-
return elt__default[
|
|
310
|
+
return elt__default["default"]("span", { class: "cm-lintPoint cm-lintPoint-" + this.diagnostic.severity });
|
|
303
311
|
}
|
|
304
312
|
}
|
|
305
313
|
class PanelItem {
|
|
@@ -355,14 +363,14 @@ class LintPanel {
|
|
|
355
363
|
this.moveSelection(i);
|
|
356
364
|
}
|
|
357
365
|
};
|
|
358
|
-
this.list = elt__default[
|
|
366
|
+
this.list = elt__default["default"]("ul", {
|
|
359
367
|
tabIndex: 0,
|
|
360
368
|
role: "listbox",
|
|
361
369
|
"aria-label": this.view.state.phrase("Diagnostics"),
|
|
362
370
|
onkeydown,
|
|
363
371
|
onclick
|
|
364
372
|
});
|
|
365
|
-
this.dom = elt__default[
|
|
373
|
+
this.dom = elt__default["default"]("div", { class: "cm-panel-lint" }, this.list, elt__default["default"]("button", {
|
|
366
374
|
type: "button",
|
|
367
375
|
name: "close",
|
|
368
376
|
"aria-label": this.view.state.phrase("close"),
|
|
@@ -478,13 +486,11 @@ class LintPanel {
|
|
|
478
486
|
}
|
|
479
487
|
static open(view) { return new LintPanel(view); }
|
|
480
488
|
}
|
|
489
|
+
function svg(content, attrs = `viewBox="0 0 40 40"`) {
|
|
490
|
+
return `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${encodeURIComponent(content)}</svg>')`;
|
|
491
|
+
}
|
|
481
492
|
function underline(color) {
|
|
482
|
-
|
|
483
|
-
return "none";
|
|
484
|
-
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">
|
|
485
|
-
<path d="m0 3 l2 -2 l1 0 l2 2 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>
|
|
486
|
-
</svg>`;
|
|
487
|
-
return `url('data:image/svg+xml;base64,${btoa(svg)}')`;
|
|
493
|
+
return svg(`<path d="m0 2.5 l2 -1.5 l1 0 l2 1.5 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>`, `width="6" height="3"`);
|
|
488
494
|
}
|
|
489
495
|
const baseTheme = view.EditorView.baseTheme({
|
|
490
496
|
".cm-diagnostic": {
|
|
@@ -511,7 +517,8 @@ const baseTheme = view.EditorView.baseTheme({
|
|
|
511
517
|
},
|
|
512
518
|
".cm-lintRange": {
|
|
513
519
|
backgroundPosition: "left bottom",
|
|
514
|
-
backgroundRepeat: "repeat-x"
|
|
520
|
+
backgroundRepeat: "repeat-x",
|
|
521
|
+
paddingBottom: "0.7px",
|
|
515
522
|
},
|
|
516
523
|
".cm-lintRange-error": { backgroundImage: underline("#d11") },
|
|
517
524
|
".cm-lintRange-warning": { backgroundImage: underline("orange") },
|
|
@@ -570,12 +577,151 @@ const baseTheme = view.EditorView.baseTheme({
|
|
|
570
577
|
}
|
|
571
578
|
}
|
|
572
579
|
});
|
|
580
|
+
class LintGutterMarker extends gutter.GutterMarker {
|
|
581
|
+
constructor(diagnostics) {
|
|
582
|
+
super();
|
|
583
|
+
this.diagnostics = diagnostics;
|
|
584
|
+
this.severity = diagnostics.reduce((max, d) => {
|
|
585
|
+
let s = d.severity;
|
|
586
|
+
return s == "error" || s == "warning" && max == "info" ? s : max;
|
|
587
|
+
}, "info");
|
|
588
|
+
}
|
|
589
|
+
toDOM(view) {
|
|
590
|
+
let elt = document.createElement("div");
|
|
591
|
+
elt.className = "cm-lint-marker cm-lint-marker-" + this.severity;
|
|
592
|
+
elt.onmouseover = () => gutterMarkerMouseOver(view, elt, this.diagnostics);
|
|
593
|
+
return elt;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function trackHoverOn(view, marker) {
|
|
597
|
+
let mousemove = (event) => {
|
|
598
|
+
let rect = marker.getBoundingClientRect();
|
|
599
|
+
if (event.clientX > rect.left - 10 /* Margin */ && event.clientX < rect.right + 10 /* Margin */ &&
|
|
600
|
+
event.clientY > rect.top - 10 /* Margin */ && event.clientY < rect.bottom + 10 /* Margin */)
|
|
601
|
+
return;
|
|
602
|
+
for (let target = event.target; target; target = target.parentNode) {
|
|
603
|
+
if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint"))
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
window.removeEventListener("mousemove", mousemove);
|
|
607
|
+
if (view.state.field(lintGutterTooltip))
|
|
608
|
+
view.dispatch({ effects: setLintGutterTooltip.of(null) });
|
|
609
|
+
};
|
|
610
|
+
window.addEventListener("mousemove", mousemove);
|
|
611
|
+
}
|
|
612
|
+
function gutterMarkerMouseOver(view, marker, diagnostics) {
|
|
613
|
+
function hovered() {
|
|
614
|
+
let line = view.visualLineAtHeight(marker.getBoundingClientRect().top + 5 - view.documentTop);
|
|
615
|
+
const linePos = view.coordsAtPos(line.from);
|
|
616
|
+
if (linePos) {
|
|
617
|
+
view.dispatch({ effects: setLintGutterTooltip.of({
|
|
618
|
+
pos: line.from,
|
|
619
|
+
above: false,
|
|
620
|
+
create() {
|
|
621
|
+
return {
|
|
622
|
+
dom: diagnosticsTooltip(view, diagnostics),
|
|
623
|
+
getCoords: () => marker.getBoundingClientRect()
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
}) });
|
|
627
|
+
}
|
|
628
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
629
|
+
trackHoverOn(view, marker);
|
|
630
|
+
}
|
|
631
|
+
let { hoverTime } = view.state.facet(lintGutterConfig);
|
|
632
|
+
let hoverTimeout = setTimeout(hovered, hoverTime);
|
|
633
|
+
marker.onmouseout = () => {
|
|
634
|
+
clearTimeout(hoverTimeout);
|
|
635
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
636
|
+
};
|
|
637
|
+
marker.onmousemove = () => {
|
|
638
|
+
clearTimeout(hoverTimeout);
|
|
639
|
+
hoverTimeout = setTimeout(hovered, hoverTime);
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function markersForDiagnostics(doc, diagnostics) {
|
|
643
|
+
let byLine = Object.create(null);
|
|
644
|
+
for (let diagnostic of diagnostics) {
|
|
645
|
+
let line = doc.lineAt(diagnostic.from);
|
|
646
|
+
(byLine[line.from] || (byLine[line.from] = [])).push(diagnostic);
|
|
647
|
+
}
|
|
648
|
+
let markers = [];
|
|
649
|
+
for (let line in byLine) {
|
|
650
|
+
markers.push(new LintGutterMarker(byLine[line]).range(+line));
|
|
651
|
+
}
|
|
652
|
+
return rangeset.RangeSet.of(markers, true);
|
|
653
|
+
}
|
|
654
|
+
const lintGutterExtension = gutter.gutter({
|
|
655
|
+
class: "cm-gutter-lint",
|
|
656
|
+
markers: view => view.state.field(lintGutterMarkers),
|
|
657
|
+
});
|
|
658
|
+
const lintGutterMarkers = state.StateField.define({
|
|
659
|
+
create() {
|
|
660
|
+
return rangeset.RangeSet.empty;
|
|
661
|
+
},
|
|
662
|
+
update(markers, tr) {
|
|
663
|
+
markers = markers.map(tr.changes);
|
|
664
|
+
for (let effect of tr.effects)
|
|
665
|
+
if (effect.is(setDiagnosticsEffect)) {
|
|
666
|
+
markers = markersForDiagnostics(tr.state.doc, effect.value);
|
|
667
|
+
}
|
|
668
|
+
return markers;
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
const setLintGutterTooltip = state.StateEffect.define();
|
|
672
|
+
const lintGutterTooltip = state.StateField.define({
|
|
673
|
+
create() { return null; },
|
|
674
|
+
update(tooltip, tr) {
|
|
675
|
+
if (tooltip && tr.docChanged)
|
|
676
|
+
tooltip = Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
|
|
677
|
+
return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
|
|
678
|
+
},
|
|
679
|
+
provide: field => tooltip.showTooltip.from(field)
|
|
680
|
+
});
|
|
681
|
+
const lintGutterTheme = view.EditorView.baseTheme({
|
|
682
|
+
".cm-gutter-lint": {
|
|
683
|
+
width: "1.4em",
|
|
684
|
+
"& .cm-gutterElement": {
|
|
685
|
+
padding: ".2em"
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
".cm-lint-marker": {
|
|
689
|
+
width: "1em",
|
|
690
|
+
height: "1em"
|
|
691
|
+
},
|
|
692
|
+
".cm-lint-marker-info": {
|
|
693
|
+
content: svg(`<path fill="#aaf" stroke="#77e" stroke-width="6" stroke-linejoin="round" d="M5 5L35 5L35 35L5 35Z"/>`)
|
|
694
|
+
},
|
|
695
|
+
".cm-lint-marker-warning": {
|
|
696
|
+
content: svg(`<path fill="#fe8" stroke="#fd7" stroke-width="6" stroke-linejoin="round" d="M20 6L37 35L3 35Z"/>`),
|
|
697
|
+
},
|
|
698
|
+
".cm-lint-marker-error:before": {
|
|
699
|
+
content: svg(`<circle cx="20" cy="20" r="15" fill="#f87" stroke="#f43" stroke-width="6"/>`)
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
const lintGutterConfig = state.Facet.define({
|
|
703
|
+
combine(configs) {
|
|
704
|
+
return state.combineConfig(configs, {
|
|
705
|
+
hoverTime: 300 /* Time */,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
/**
|
|
710
|
+
Returns an extension that installs a gutter showing markers for
|
|
711
|
+
each line that has diagnostics, which can be hovered over to see
|
|
712
|
+
the diagnostics.
|
|
713
|
+
*/
|
|
714
|
+
function lintGutter(config = {}) {
|
|
715
|
+
return [lintGutterConfig.of(config), lintGutterMarkers, lintGutterExtension, lintGutterTheme, lintGutterTooltip];
|
|
716
|
+
}
|
|
573
717
|
|
|
574
718
|
exports.closeLintPanel = closeLintPanel;
|
|
575
719
|
exports.diagnosticCount = diagnosticCount;
|
|
576
720
|
exports.forceLinting = forceLinting;
|
|
721
|
+
exports.lintGutter = lintGutter;
|
|
577
722
|
exports.lintKeymap = lintKeymap;
|
|
578
723
|
exports.linter = linter;
|
|
579
724
|
exports.nextDiagnostic = nextDiagnostic;
|
|
580
725
|
exports.openLintPanel = openLintPanel;
|
|
581
726
|
exports.setDiagnostics = setDiagnostics;
|
|
727
|
+
exports.setDiagnosticsEffect = setDiagnosticsEffect;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _codemirror_state from '@codemirror/state';
|
|
2
2
|
import { EditorState, TransactionSpec, Extension } from '@codemirror/state';
|
|
3
|
+
import { EditorView, Command, KeyBinding } from '@codemirror/view';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
Describes a problem or hint for a piece of code.
|
|
@@ -50,12 +51,24 @@ interface Action {
|
|
|
50
51
|
*/
|
|
51
52
|
apply: (view: EditorView, from: number, to: number) => void;
|
|
52
53
|
}
|
|
54
|
+
interface LintGutterConfig {
|
|
55
|
+
/**
|
|
56
|
+
The delay before showing a tooltip when hovering over a lint gutter marker.
|
|
57
|
+
*/
|
|
58
|
+
hoverTime?: number;
|
|
59
|
+
}
|
|
53
60
|
/**
|
|
54
61
|
Returns a transaction spec which updates the current set of
|
|
55
|
-
diagnostics
|
|
62
|
+
diagnostics, and enables the lint extension if if wasn't already
|
|
63
|
+
active.
|
|
56
64
|
*/
|
|
57
65
|
declare function setDiagnostics(state: EditorState, diagnostics: readonly Diagnostic[]): TransactionSpec;
|
|
58
66
|
/**
|
|
67
|
+
The state effect that updates the set of active diagnostics. Can
|
|
68
|
+
be useful when writing an extension that needs to track these.
|
|
69
|
+
*/
|
|
70
|
+
declare const setDiagnosticsEffect: _codemirror_state.StateEffectType<readonly Diagnostic[]>;
|
|
71
|
+
/**
|
|
59
72
|
Returns the number of active lint diagnostics in the given state.
|
|
60
73
|
*/
|
|
61
74
|
declare function diagnosticCount(state: EditorState): number;
|
|
@@ -96,5 +109,11 @@ Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter)
|
|
|
96
109
|
editor is idle to run right away.
|
|
97
110
|
*/
|
|
98
111
|
declare function forceLinting(view: EditorView): void;
|
|
112
|
+
/**
|
|
113
|
+
Returns an extension that installs a gutter showing markers for
|
|
114
|
+
each line that has diagnostics, which can be hovered over to see
|
|
115
|
+
the diagnostics.
|
|
116
|
+
*/
|
|
117
|
+
declare function lintGutter(config?: LintGutterConfig): Extension;
|
|
99
118
|
|
|
100
|
-
export { Action, Diagnostic, closeLintPanel, diagnosticCount, forceLinting, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics };
|
|
119
|
+
export { Action, Diagnostic, closeLintPanel, diagnosticCount, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Decoration, EditorView, ViewPlugin, logException, WidgetType } from '@codemirror/view';
|
|
2
|
-
import { StateEffect, StateField, Facet } from '@codemirror/state';
|
|
3
|
-
import { hoverTooltip } from '@codemirror/tooltip';
|
|
2
|
+
import { StateEffect, StateField, Facet, combineConfig } from '@codemirror/state';
|
|
3
|
+
import { hoverTooltip, showTooltip } from '@codemirror/tooltip';
|
|
4
4
|
import { showPanel, getPanel } from '@codemirror/panel';
|
|
5
|
+
import { gutter, GutterMarker } from '@codemirror/gutter';
|
|
6
|
+
import { RangeSet } from '@codemirror/rangeset';
|
|
5
7
|
import elt from 'crelt';
|
|
6
8
|
|
|
7
9
|
class SelectedDiagnostic {
|
|
@@ -43,9 +45,9 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
|
|
|
43
45
|
});
|
|
44
46
|
return found;
|
|
45
47
|
}
|
|
46
|
-
function maybeEnableLint(state, effects
|
|
48
|
+
function maybeEnableLint(state, effects) {
|
|
47
49
|
return state.field(lintState, false) ? effects : effects.concat(StateEffect.appendConfig.of([
|
|
48
|
-
lintState
|
|
50
|
+
lintState,
|
|
49
51
|
EditorView.decorations.compute([lintState], state => {
|
|
50
52
|
let { selected, panel } = state.field(lintState);
|
|
51
53
|
return !selected || !panel || selected.from == selected.to ? Decoration.none : Decoration.set([
|
|
@@ -58,13 +60,18 @@ function maybeEnableLint(state, effects, getState) {
|
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
62
|
Returns a transaction spec which updates the current set of
|
|
61
|
-
diagnostics
|
|
63
|
+
diagnostics, and enables the lint extension if if wasn't already
|
|
64
|
+
active.
|
|
62
65
|
*/
|
|
63
66
|
function setDiagnostics(state, diagnostics) {
|
|
64
67
|
return {
|
|
65
|
-
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)]
|
|
68
|
+
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)])
|
|
66
69
|
};
|
|
67
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
The state effect that updates the set of active diagnostics. Can
|
|
73
|
+
be useful when writing an extension that needs to track these.
|
|
74
|
+
*/
|
|
68
75
|
const setDiagnosticsEffect = /*@__PURE__*/StateEffect.define();
|
|
69
76
|
const togglePanel = /*@__PURE__*/StateEffect.define();
|
|
70
77
|
const movePanelSelection = /*@__PURE__*/StateEffect.define();
|
|
@@ -123,17 +130,20 @@ function lintTooltip(view, pos, side) {
|
|
|
123
130
|
end: stackEnd,
|
|
124
131
|
above: view.state.doc.lineAt(stackStart).to < stackEnd,
|
|
125
132
|
create() {
|
|
126
|
-
return { dom:
|
|
133
|
+
return { dom: diagnosticsTooltip(view, found) };
|
|
127
134
|
}
|
|
128
135
|
};
|
|
129
136
|
}
|
|
137
|
+
function diagnosticsTooltip(view, diagnostics) {
|
|
138
|
+
return elt("ul", { class: "cm-tooltip-lint" }, diagnostics.map(d => renderDiagnostic(view, d, false)));
|
|
139
|
+
}
|
|
130
140
|
/**
|
|
131
141
|
Command to open and focus the lint panel.
|
|
132
142
|
*/
|
|
133
143
|
const openLintPanel = (view) => {
|
|
134
144
|
let field = view.state.field(lintState, false);
|
|
135
145
|
if (!field || !field.panel)
|
|
136
|
-
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]
|
|
146
|
+
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]) });
|
|
137
147
|
let panel = getPanel(view, LintPanel.open);
|
|
138
148
|
if (panel)
|
|
139
149
|
panel.dom.querySelector(".cm-panel-lint ul").focus();
|
|
@@ -194,10 +204,8 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
|
194
204
|
this.set = false;
|
|
195
205
|
let { state } = this.view, { sources } = state.facet(lintSource);
|
|
196
206
|
Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => {
|
|
197
|
-
var _a, _b;
|
|
198
207
|
let all = annotations.reduce((a, b) => a.concat(b));
|
|
199
|
-
if (this.view.state.doc == state.doc
|
|
200
|
-
(all.length || ((_b = (_a = this.view.state.field(lintState, false)) === null || _a === void 0 ? void 0 : _a.diagnostics) === null || _b === void 0 ? void 0 : _b.size)))
|
|
208
|
+
if (this.view.state.doc == state.doc)
|
|
201
209
|
this.view.dispatch(setDiagnostics(this.view.state, all));
|
|
202
210
|
}, error => { logException(this.view.state, error); });
|
|
203
211
|
}
|
|
@@ -470,13 +478,11 @@ class LintPanel {
|
|
|
470
478
|
}
|
|
471
479
|
static open(view) { return new LintPanel(view); }
|
|
472
480
|
}
|
|
481
|
+
function svg(content, attrs = `viewBox="0 0 40 40"`) {
|
|
482
|
+
return `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${encodeURIComponent(content)}</svg>')`;
|
|
483
|
+
}
|
|
473
484
|
function underline(color) {
|
|
474
|
-
|
|
475
|
-
return "none";
|
|
476
|
-
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">
|
|
477
|
-
<path d="m0 3 l2 -2 l1 0 l2 2 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>
|
|
478
|
-
</svg>`;
|
|
479
|
-
return `url('data:image/svg+xml;base64,${btoa(svg)}')`;
|
|
485
|
+
return svg(`<path d="m0 2.5 l2 -1.5 l1 0 l2 1.5 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>`, `width="6" height="3"`);
|
|
480
486
|
}
|
|
481
487
|
const baseTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
482
488
|
".cm-diagnostic": {
|
|
@@ -503,7 +509,8 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
|
503
509
|
},
|
|
504
510
|
".cm-lintRange": {
|
|
505
511
|
backgroundPosition: "left bottom",
|
|
506
|
-
backgroundRepeat: "repeat-x"
|
|
512
|
+
backgroundRepeat: "repeat-x",
|
|
513
|
+
paddingBottom: "0.7px",
|
|
507
514
|
},
|
|
508
515
|
".cm-lintRange-error": { backgroundImage: /*@__PURE__*/underline("#d11") },
|
|
509
516
|
".cm-lintRange-warning": { backgroundImage: /*@__PURE__*/underline("orange") },
|
|
@@ -562,5 +569,142 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
|
562
569
|
}
|
|
563
570
|
}
|
|
564
571
|
});
|
|
572
|
+
class LintGutterMarker extends GutterMarker {
|
|
573
|
+
constructor(diagnostics) {
|
|
574
|
+
super();
|
|
575
|
+
this.diagnostics = diagnostics;
|
|
576
|
+
this.severity = diagnostics.reduce((max, d) => {
|
|
577
|
+
let s = d.severity;
|
|
578
|
+
return s == "error" || s == "warning" && max == "info" ? s : max;
|
|
579
|
+
}, "info");
|
|
580
|
+
}
|
|
581
|
+
toDOM(view) {
|
|
582
|
+
let elt = document.createElement("div");
|
|
583
|
+
elt.className = "cm-lint-marker cm-lint-marker-" + this.severity;
|
|
584
|
+
elt.onmouseover = () => gutterMarkerMouseOver(view, elt, this.diagnostics);
|
|
585
|
+
return elt;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function trackHoverOn(view, marker) {
|
|
589
|
+
let mousemove = (event) => {
|
|
590
|
+
let rect = marker.getBoundingClientRect();
|
|
591
|
+
if (event.clientX > rect.left - 10 /* Margin */ && event.clientX < rect.right + 10 /* Margin */ &&
|
|
592
|
+
event.clientY > rect.top - 10 /* Margin */ && event.clientY < rect.bottom + 10 /* Margin */)
|
|
593
|
+
return;
|
|
594
|
+
for (let target = event.target; target; target = target.parentNode) {
|
|
595
|
+
if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint"))
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
window.removeEventListener("mousemove", mousemove);
|
|
599
|
+
if (view.state.field(lintGutterTooltip))
|
|
600
|
+
view.dispatch({ effects: setLintGutterTooltip.of(null) });
|
|
601
|
+
};
|
|
602
|
+
window.addEventListener("mousemove", mousemove);
|
|
603
|
+
}
|
|
604
|
+
function gutterMarkerMouseOver(view, marker, diagnostics) {
|
|
605
|
+
function hovered() {
|
|
606
|
+
let line = view.visualLineAtHeight(marker.getBoundingClientRect().top + 5 - view.documentTop);
|
|
607
|
+
const linePos = view.coordsAtPos(line.from);
|
|
608
|
+
if (linePos) {
|
|
609
|
+
view.dispatch({ effects: setLintGutterTooltip.of({
|
|
610
|
+
pos: line.from,
|
|
611
|
+
above: false,
|
|
612
|
+
create() {
|
|
613
|
+
return {
|
|
614
|
+
dom: diagnosticsTooltip(view, diagnostics),
|
|
615
|
+
getCoords: () => marker.getBoundingClientRect()
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
}) });
|
|
619
|
+
}
|
|
620
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
621
|
+
trackHoverOn(view, marker);
|
|
622
|
+
}
|
|
623
|
+
let { hoverTime } = view.state.facet(lintGutterConfig);
|
|
624
|
+
let hoverTimeout = setTimeout(hovered, hoverTime);
|
|
625
|
+
marker.onmouseout = () => {
|
|
626
|
+
clearTimeout(hoverTimeout);
|
|
627
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
628
|
+
};
|
|
629
|
+
marker.onmousemove = () => {
|
|
630
|
+
clearTimeout(hoverTimeout);
|
|
631
|
+
hoverTimeout = setTimeout(hovered, hoverTime);
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function markersForDiagnostics(doc, diagnostics) {
|
|
635
|
+
let byLine = Object.create(null);
|
|
636
|
+
for (let diagnostic of diagnostics) {
|
|
637
|
+
let line = doc.lineAt(diagnostic.from);
|
|
638
|
+
(byLine[line.from] || (byLine[line.from] = [])).push(diagnostic);
|
|
639
|
+
}
|
|
640
|
+
let markers = [];
|
|
641
|
+
for (let line in byLine) {
|
|
642
|
+
markers.push(new LintGutterMarker(byLine[line]).range(+line));
|
|
643
|
+
}
|
|
644
|
+
return RangeSet.of(markers, true);
|
|
645
|
+
}
|
|
646
|
+
const lintGutterExtension = /*@__PURE__*/gutter({
|
|
647
|
+
class: "cm-gutter-lint",
|
|
648
|
+
markers: view => view.state.field(lintGutterMarkers),
|
|
649
|
+
});
|
|
650
|
+
const lintGutterMarkers = /*@__PURE__*/StateField.define({
|
|
651
|
+
create() {
|
|
652
|
+
return RangeSet.empty;
|
|
653
|
+
},
|
|
654
|
+
update(markers, tr) {
|
|
655
|
+
markers = markers.map(tr.changes);
|
|
656
|
+
for (let effect of tr.effects)
|
|
657
|
+
if (effect.is(setDiagnosticsEffect)) {
|
|
658
|
+
markers = markersForDiagnostics(tr.state.doc, effect.value);
|
|
659
|
+
}
|
|
660
|
+
return markers;
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
const setLintGutterTooltip = /*@__PURE__*/StateEffect.define();
|
|
664
|
+
const lintGutterTooltip = /*@__PURE__*/StateField.define({
|
|
665
|
+
create() { return null; },
|
|
666
|
+
update(tooltip, tr) {
|
|
667
|
+
if (tooltip && tr.docChanged)
|
|
668
|
+
tooltip = Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
|
|
669
|
+
return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
|
|
670
|
+
},
|
|
671
|
+
provide: field => showTooltip.from(field)
|
|
672
|
+
});
|
|
673
|
+
const lintGutterTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
674
|
+
".cm-gutter-lint": {
|
|
675
|
+
width: "1.4em",
|
|
676
|
+
"& .cm-gutterElement": {
|
|
677
|
+
padding: ".2em"
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
".cm-lint-marker": {
|
|
681
|
+
width: "1em",
|
|
682
|
+
height: "1em"
|
|
683
|
+
},
|
|
684
|
+
".cm-lint-marker-info": {
|
|
685
|
+
content: /*@__PURE__*/svg(`<path fill="#aaf" stroke="#77e" stroke-width="6" stroke-linejoin="round" d="M5 5L35 5L35 35L5 35Z"/>`)
|
|
686
|
+
},
|
|
687
|
+
".cm-lint-marker-warning": {
|
|
688
|
+
content: /*@__PURE__*/svg(`<path fill="#fe8" stroke="#fd7" stroke-width="6" stroke-linejoin="round" d="M20 6L37 35L3 35Z"/>`),
|
|
689
|
+
},
|
|
690
|
+
".cm-lint-marker-error:before": {
|
|
691
|
+
content: /*@__PURE__*/svg(`<circle cx="20" cy="20" r="15" fill="#f87" stroke="#f43" stroke-width="6"/>`)
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
const lintGutterConfig = /*@__PURE__*/Facet.define({
|
|
695
|
+
combine(configs) {
|
|
696
|
+
return combineConfig(configs, {
|
|
697
|
+
hoverTime: 300 /* Time */,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
/**
|
|
702
|
+
Returns an extension that installs a gutter showing markers for
|
|
703
|
+
each line that has diagnostics, which can be hovered over to see
|
|
704
|
+
the diagnostics.
|
|
705
|
+
*/
|
|
706
|
+
function lintGutter(config = {}) {
|
|
707
|
+
return [lintGutterConfig.of(config), lintGutterMarkers, lintGutterExtension, lintGutterTheme, lintGutterTooltip];
|
|
708
|
+
}
|
|
565
709
|
|
|
566
|
-
export { closeLintPanel, diagnosticCount, forceLinting, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics };
|
|
710
|
+
export { closeLintPanel, diagnosticCount, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemirror/lint",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.5",
|
|
4
4
|
"description": "Linting support for the CodeMirror code editor",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "cm-runtests",
|
|
@@ -26,10 +26,12 @@
|
|
|
26
26
|
"sideEffects": false,
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@codemirror/gutter": "^0.19.4",
|
|
29
30
|
"@codemirror/panel": "^0.19.0",
|
|
30
|
-
"@codemirror/
|
|
31
|
-
"@codemirror/
|
|
32
|
-
"@codemirror/
|
|
31
|
+
"@codemirror/rangeset": "^0.19.1",
|
|
32
|
+
"@codemirror/state": "^0.19.4",
|
|
33
|
+
"@codemirror/tooltip": "^0.19.16",
|
|
34
|
+
"@codemirror/view": "^0.19.22",
|
|
33
35
|
"crelt": "^1.0.5"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|