@codemirror/lint 0.18.6 → 0.19.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 +30 -0
- package/dist/index.cjs +180 -26
- package/dist/index.d.ts +20 -3
- package/dist/index.js +180 -29
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## 0.19.3 (2021-11-09)
|
|
2
|
+
|
|
3
|
+
### New features
|
|
4
|
+
|
|
5
|
+
Export a function `lintGutter` which returns an extension that installs a gutter marking lines with diagnostics.
|
|
6
|
+
|
|
7
|
+
The package now exports the effect used to update the diagnostics (`setDiagnosticsEffect`).
|
|
8
|
+
|
|
9
|
+
## 0.19.2 (2021-09-29)
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
Fix a bug where reconfiguring the lint source didn't restart linting.
|
|
14
|
+
|
|
15
|
+
## 0.19.1 (2021-09-17)
|
|
16
|
+
|
|
17
|
+
### Bug fixes
|
|
18
|
+
|
|
19
|
+
Prevent decorations that cover just a line break from being invisible by showing a widget instead of range for them.
|
|
20
|
+
|
|
21
|
+
### New features
|
|
22
|
+
|
|
23
|
+
The `diagnosticCount` method can now be used to determine whether there are active diagnostics.
|
|
24
|
+
|
|
25
|
+
## 0.19.0 (2021-08-11)
|
|
26
|
+
|
|
27
|
+
### Breaking changes
|
|
28
|
+
|
|
29
|
+
Update dependencies to 0.19.0
|
|
30
|
+
|
|
1
31
|
## 0.18.6 (2021-08-08)
|
|
2
32
|
|
|
3
33
|
### 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 }; }
|
|
@@ -25,17 +27,18 @@ class LintState {
|
|
|
25
27
|
this.panel = panel;
|
|
26
28
|
this.selected = selected;
|
|
27
29
|
}
|
|
28
|
-
static init(diagnostics, panel) {
|
|
30
|
+
static init(diagnostics, panel, state) {
|
|
29
31
|
let ranges = view.Decoration.set(diagnostics.map((d) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
diagnostic: d
|
|
34
|
-
}).range(d.from, d.to)
|
|
35
|
-
: view.Decoration.widget({
|
|
32
|
+
// For zero-length ranges or ranges covering only a line break, create a widget
|
|
33
|
+
return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
|
|
34
|
+
? view.Decoration.widget({
|
|
36
35
|
widget: new DiagnosticWidget(d),
|
|
37
36
|
diagnostic: d
|
|
38
|
-
}).range(d.from)
|
|
37
|
+
}).range(d.from)
|
|
38
|
+
: view.Decoration.mark({
|
|
39
|
+
attributes: { class: "cm-lintRange cm-lintRange-" + d.severity },
|
|
40
|
+
diagnostic: d
|
|
41
|
+
}).range(d.from, d.to);
|
|
39
42
|
}), true);
|
|
40
43
|
return new LintState(ranges, panel, findDiagnostic(ranges));
|
|
41
44
|
}
|
|
@@ -50,9 +53,9 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
|
|
|
50
53
|
});
|
|
51
54
|
return found;
|
|
52
55
|
}
|
|
53
|
-
function maybeEnableLint(state$1, effects
|
|
56
|
+
function maybeEnableLint(state$1, effects) {
|
|
54
57
|
return state$1.field(lintState, false) ? effects : effects.concat(state.StateEffect.appendConfig.of([
|
|
55
|
-
lintState
|
|
58
|
+
lintState,
|
|
56
59
|
view.EditorView.decorations.compute([lintState], state => {
|
|
57
60
|
let { selected, panel } = state.field(lintState);
|
|
58
61
|
return !selected || !panel || selected.from == selected.to ? view.Decoration.none : view.Decoration.set([
|
|
@@ -65,13 +68,18 @@ function maybeEnableLint(state$1, effects, getState) {
|
|
|
65
68
|
}
|
|
66
69
|
/**
|
|
67
70
|
Returns a transaction spec which updates the current set of
|
|
68
|
-
diagnostics
|
|
71
|
+
diagnostics, and enables the lint extension if if wasn't already
|
|
72
|
+
active.
|
|
69
73
|
*/
|
|
70
74
|
function setDiagnostics(state, diagnostics) {
|
|
71
75
|
return {
|
|
72
|
-
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)]
|
|
76
|
+
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)])
|
|
73
77
|
};
|
|
74
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
|
+
*/
|
|
75
83
|
const setDiagnosticsEffect = state.StateEffect.define();
|
|
76
84
|
const togglePanel = state.StateEffect.define();
|
|
77
85
|
const movePanelSelection = state.StateEffect.define();
|
|
@@ -90,7 +98,7 @@ const lintState = state.StateField.define({
|
|
|
90
98
|
}
|
|
91
99
|
for (let effect of tr.effects) {
|
|
92
100
|
if (effect.is(setDiagnosticsEffect)) {
|
|
93
|
-
value = LintState.init(effect.value, value.panel);
|
|
101
|
+
value = LintState.init(effect.value, value.panel, tr.state);
|
|
94
102
|
}
|
|
95
103
|
else if (effect.is(togglePanel)) {
|
|
96
104
|
value = new LintState(value.diagnostics, effect.value ? LintPanel.open : null, value.selected);
|
|
@@ -104,6 +112,13 @@ const lintState = state.StateField.define({
|
|
|
104
112
|
provide: f => [panel.showPanel.from(f, val => val.panel),
|
|
105
113
|
view.EditorView.decorations.from(f, s => s.diagnostics)]
|
|
106
114
|
});
|
|
115
|
+
/**
|
|
116
|
+
Returns the number of active lint diagnostics in the given state.
|
|
117
|
+
*/
|
|
118
|
+
function diagnosticCount(state) {
|
|
119
|
+
let lint = state.field(lintState, false);
|
|
120
|
+
return lint ? lint.diagnostics.size : 0;
|
|
121
|
+
}
|
|
107
122
|
const activeMark = view.Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
|
|
108
123
|
function lintTooltip(view, pos, side) {
|
|
109
124
|
let { diagnostics } = view.state.field(lintState);
|
|
@@ -123,17 +138,20 @@ function lintTooltip(view, pos, side) {
|
|
|
123
138
|
end: stackEnd,
|
|
124
139
|
above: view.state.doc.lineAt(stackStart).to < stackEnd,
|
|
125
140
|
create() {
|
|
126
|
-
return { dom:
|
|
141
|
+
return { dom: diagnosticsTooltip(view, found) };
|
|
127
142
|
}
|
|
128
143
|
};
|
|
129
144
|
}
|
|
145
|
+
function diagnosticsTooltip(view, diagnostics) {
|
|
146
|
+
return elt__default['default']("ul", { class: "cm-tooltip-lint" }, diagnostics.map(d => renderDiagnostic(view, d, false)));
|
|
147
|
+
}
|
|
130
148
|
/**
|
|
131
149
|
Command to open and focus the lint panel.
|
|
132
150
|
*/
|
|
133
151
|
const openLintPanel = (view) => {
|
|
134
152
|
let field = view.state.field(lintState, false);
|
|
135
153
|
if (!field || !field.panel)
|
|
136
|
-
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]
|
|
154
|
+
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]) });
|
|
137
155
|
let panel$1 = panel.getPanel(view, LintPanel.open);
|
|
138
156
|
if (panel$1)
|
|
139
157
|
panel$1.dom.querySelector(".cm-panel-lint ul").focus();
|
|
@@ -203,12 +221,12 @@ const lintPlugin = view.ViewPlugin.fromClass(class {
|
|
|
203
221
|
}
|
|
204
222
|
}
|
|
205
223
|
update(update) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this.lintTime = Date.now() + delay;
|
|
224
|
+
let source = update.state.facet(lintSource);
|
|
225
|
+
if (update.docChanged || source != update.startState.facet(lintSource)) {
|
|
226
|
+
this.lintTime = Date.now() + source.delay;
|
|
209
227
|
if (!this.set) {
|
|
210
228
|
this.set = true;
|
|
211
|
-
this.timeout = setTimeout(this.run, delay);
|
|
229
|
+
this.timeout = setTimeout(this.run, source.delay);
|
|
212
230
|
}
|
|
213
231
|
}
|
|
214
232
|
}
|
|
@@ -276,6 +294,7 @@ function renderDiagnostic(view, diagnostic, inPanel) {
|
|
|
276
294
|
elt__default['default']("u", name.slice(keyIndex, keyIndex + 1)),
|
|
277
295
|
name.slice(keyIndex + 1)];
|
|
278
296
|
return elt__default['default']("button", {
|
|
297
|
+
type: "button",
|
|
279
298
|
class: "cm-diagnosticAction",
|
|
280
299
|
onclick: click,
|
|
281
300
|
onmousedown: click,
|
|
@@ -354,6 +373,7 @@ class LintPanel {
|
|
|
354
373
|
onclick
|
|
355
374
|
});
|
|
356
375
|
this.dom = elt__default['default']("div", { class: "cm-panel-lint" }, this.list, elt__default['default']("button", {
|
|
376
|
+
type: "button",
|
|
357
377
|
name: "close",
|
|
358
378
|
"aria-label": this.view.state.phrase("close"),
|
|
359
379
|
onclick: () => closeLintPanel(this.view)
|
|
@@ -468,13 +488,11 @@ class LintPanel {
|
|
|
468
488
|
}
|
|
469
489
|
static open(view) { return new LintPanel(view); }
|
|
470
490
|
}
|
|
491
|
+
function svg(content, attrs = `viewBox="0 0 40 40"`) {
|
|
492
|
+
return `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${encodeURIComponent(content)}</svg>')`;
|
|
493
|
+
}
|
|
471
494
|
function underline(color) {
|
|
472
|
-
|
|
473
|
-
return "none";
|
|
474
|
-
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">
|
|
475
|
-
<path d="m0 3 l2 -2 l1 0 l2 2 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>
|
|
476
|
-
</svg>`;
|
|
477
|
-
return `url('data:image/svg+xml;base64,${btoa(svg)}')`;
|
|
495
|
+
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"`);
|
|
478
496
|
}
|
|
479
497
|
const baseTheme = view.EditorView.baseTheme({
|
|
480
498
|
".cm-diagnostic": {
|
|
@@ -501,7 +519,8 @@ const baseTheme = view.EditorView.baseTheme({
|
|
|
501
519
|
},
|
|
502
520
|
".cm-lintRange": {
|
|
503
521
|
backgroundPosition: "left bottom",
|
|
504
|
-
backgroundRepeat: "repeat-x"
|
|
522
|
+
backgroundRepeat: "repeat-x",
|
|
523
|
+
paddingBottom: "0.7px",
|
|
505
524
|
},
|
|
506
525
|
".cm-lintRange-error": { backgroundImage: underline("#d11") },
|
|
507
526
|
".cm-lintRange-warning": { backgroundImage: underline("orange") },
|
|
@@ -560,11 +579,146 @@ const baseTheme = view.EditorView.baseTheme({
|
|
|
560
579
|
}
|
|
561
580
|
}
|
|
562
581
|
});
|
|
582
|
+
class LintGutterMarker extends gutter.GutterMarker {
|
|
583
|
+
constructor(diagnostics) {
|
|
584
|
+
super();
|
|
585
|
+
this.diagnostics = diagnostics;
|
|
586
|
+
this.severity = diagnostics.reduce((max, d) => {
|
|
587
|
+
let s = d.severity;
|
|
588
|
+
return s == "error" || s == "warning" && max == "info" ? s : max;
|
|
589
|
+
}, "info");
|
|
590
|
+
}
|
|
591
|
+
toDOM(view) {
|
|
592
|
+
let elt = document.createElement("div");
|
|
593
|
+
elt.className = "cm-lint-marker cm-lint-marker-" + this.severity;
|
|
594
|
+
elt.onmouseover = () => gutterMarkerMouseOver(view, elt, this.diagnostics);
|
|
595
|
+
return elt;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function trackHoverOn(view, marker) {
|
|
599
|
+
let mousemove = (event) => {
|
|
600
|
+
let rect = marker.getBoundingClientRect();
|
|
601
|
+
if (event.clientX > rect.left - 10 /* Margin */ && event.clientX < rect.right + 10 /* Margin */ &&
|
|
602
|
+
event.clientY > rect.top - 10 /* Margin */ && event.clientY < rect.bottom + 10 /* Margin */)
|
|
603
|
+
return;
|
|
604
|
+
for (let target = event.target; target; target = target.parentNode) {
|
|
605
|
+
if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint"))
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
window.removeEventListener("mousemove", mousemove);
|
|
609
|
+
if (view.state.field(lintGutterTooltip))
|
|
610
|
+
view.dispatch({ effects: setLintGutterTooltip.of(null) });
|
|
611
|
+
};
|
|
612
|
+
window.addEventListener("mousemove", mousemove);
|
|
613
|
+
}
|
|
614
|
+
function gutterMarkerMouseOver(view, marker, diagnostics) {
|
|
615
|
+
function hovered() {
|
|
616
|
+
let line = view.visualLineAtHeight(marker.getBoundingClientRect().top + 5);
|
|
617
|
+
const linePos = view.coordsAtPos(line.from), markerRect = marker.getBoundingClientRect();
|
|
618
|
+
if (linePos) {
|
|
619
|
+
view.dispatch({ effects: setLintGutterTooltip.of({
|
|
620
|
+
pos: line.from,
|
|
621
|
+
above: false,
|
|
622
|
+
create() {
|
|
623
|
+
return {
|
|
624
|
+
dom: diagnosticsTooltip(view, diagnostics),
|
|
625
|
+
offset: { x: markerRect.left - linePos.left, y: 0 }
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}) });
|
|
629
|
+
}
|
|
630
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
631
|
+
trackHoverOn(view, marker);
|
|
632
|
+
}
|
|
633
|
+
let hoverTimeout = setTimeout(hovered, 600 /* Time */);
|
|
634
|
+
marker.onmouseout = () => {
|
|
635
|
+
clearTimeout(hoverTimeout);
|
|
636
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
637
|
+
};
|
|
638
|
+
marker.onmousemove = () => {
|
|
639
|
+
clearTimeout(hoverTimeout);
|
|
640
|
+
hoverTimeout = setTimeout(hovered, 600 /* Time */);
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function markersForDiagnostics(doc, diagnostics) {
|
|
644
|
+
let byLine = Object.create(null);
|
|
645
|
+
for (let diagnostic of diagnostics) {
|
|
646
|
+
let line = doc.lineAt(diagnostic.from);
|
|
647
|
+
(byLine[line.from] || (byLine[line.from] = [])).push(diagnostic);
|
|
648
|
+
}
|
|
649
|
+
let markers = [];
|
|
650
|
+
for (let line in byLine) {
|
|
651
|
+
markers.push(new LintGutterMarker(byLine[line]).range(+line));
|
|
652
|
+
}
|
|
653
|
+
return rangeset.RangeSet.of(markers, true);
|
|
654
|
+
}
|
|
655
|
+
const lintGutterExtension = gutter.gutter({
|
|
656
|
+
class: "cm-gutter-lint",
|
|
657
|
+
markers: view => view.state.field(lintGutterMarkers),
|
|
658
|
+
});
|
|
659
|
+
const lintGutterMarkers = state.StateField.define({
|
|
660
|
+
create() {
|
|
661
|
+
return rangeset.RangeSet.empty;
|
|
662
|
+
},
|
|
663
|
+
update(markers, tr) {
|
|
664
|
+
markers = markers.map(tr.changes);
|
|
665
|
+
for (let effect of tr.effects)
|
|
666
|
+
if (effect.is(setDiagnosticsEffect)) {
|
|
667
|
+
markers = markersForDiagnostics(tr.state.doc, effect.value);
|
|
668
|
+
}
|
|
669
|
+
return markers;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
const setLintGutterTooltip = state.StateEffect.define();
|
|
673
|
+
const lintGutterTooltip = state.StateField.define({
|
|
674
|
+
create() { return null; },
|
|
675
|
+
update(tooltip, tr) {
|
|
676
|
+
if (tooltip && tr.docChanged)
|
|
677
|
+
tooltip = Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
|
|
678
|
+
return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
|
|
679
|
+
},
|
|
680
|
+
provide: field => tooltip.showTooltip.from(field)
|
|
681
|
+
});
|
|
682
|
+
const lintGutterTheme = view.EditorView.baseTheme({
|
|
683
|
+
".cm-gutter-lint": {
|
|
684
|
+
width: "1.4em",
|
|
685
|
+
"& .cm-gutterElement": {
|
|
686
|
+
padding: "0 .2em",
|
|
687
|
+
display: "flex",
|
|
688
|
+
flexDirection: "column",
|
|
689
|
+
justifyContent: "center",
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
".cm-lint-marker": {
|
|
693
|
+
width: "1em",
|
|
694
|
+
height: "1em",
|
|
695
|
+
},
|
|
696
|
+
".cm-lint-marker-info": {
|
|
697
|
+
content: svg(`<path fill="#aaf" stroke="#77e" stroke-width="6" stroke-linejoin="round" d="M5 5L35 5L35 35L5 35Z"/>`)
|
|
698
|
+
},
|
|
699
|
+
".cm-lint-marker-warning": {
|
|
700
|
+
content: svg(`<path fill="#fe8" stroke="#fd7" stroke-width="6" stroke-linejoin="round" d="M20 6L37 35L3 35Z"/>`),
|
|
701
|
+
},
|
|
702
|
+
".cm-lint-marker-error:before": {
|
|
703
|
+
content: svg(`<circle cx="20" cy="20" r="15" fill="#f87" stroke="#f43" stroke-width="6"/>`)
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
/**
|
|
707
|
+
Returns an extension that installs a gutter showing markers for
|
|
708
|
+
each line that has diagnostics, which can be hovered over to see
|
|
709
|
+
the diagnostics.
|
|
710
|
+
*/
|
|
711
|
+
function lintGutter() {
|
|
712
|
+
return [lintGutterMarkers, lintGutterExtension, lintGutterTheme, lintGutterTooltip];
|
|
713
|
+
}
|
|
563
714
|
|
|
564
715
|
exports.closeLintPanel = closeLintPanel;
|
|
716
|
+
exports.diagnosticCount = diagnosticCount;
|
|
565
717
|
exports.forceLinting = forceLinting;
|
|
718
|
+
exports.lintGutter = lintGutter;
|
|
566
719
|
exports.lintKeymap = lintKeymap;
|
|
567
720
|
exports.linter = linter;
|
|
568
721
|
exports.nextDiagnostic = nextDiagnostic;
|
|
569
722
|
exports.openLintPanel = openLintPanel;
|
|
570
723
|
exports.setDiagnostics = setDiagnostics;
|
|
724
|
+
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.
|
|
@@ -52,10 +53,20 @@ interface Action {
|
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
54
55
|
Returns a transaction spec which updates the current set of
|
|
55
|
-
diagnostics
|
|
56
|
+
diagnostics, and enables the lint extension if if wasn't already
|
|
57
|
+
active.
|
|
56
58
|
*/
|
|
57
59
|
declare function setDiagnostics(state: EditorState, diagnostics: readonly Diagnostic[]): TransactionSpec;
|
|
58
60
|
/**
|
|
61
|
+
The state effect that updates the set of active diagnostics. Can
|
|
62
|
+
be useful when writing an extension that needs to track these.
|
|
63
|
+
*/
|
|
64
|
+
declare const setDiagnosticsEffect: _codemirror_state.StateEffectType<readonly Diagnostic[]>;
|
|
65
|
+
/**
|
|
66
|
+
Returns the number of active lint diagnostics in the given state.
|
|
67
|
+
*/
|
|
68
|
+
declare function diagnosticCount(state: EditorState): number;
|
|
69
|
+
/**
|
|
59
70
|
Command to open and focus the lint panel.
|
|
60
71
|
*/
|
|
61
72
|
declare const openLintPanel: Command;
|
|
@@ -92,5 +103,11 @@ Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter)
|
|
|
92
103
|
editor is idle to run right away.
|
|
93
104
|
*/
|
|
94
105
|
declare function forceLinting(view: EditorView): void;
|
|
106
|
+
/**
|
|
107
|
+
Returns an extension that installs a gutter showing markers for
|
|
108
|
+
each line that has diagnostics, which can be hovered over to see
|
|
109
|
+
the diagnostics.
|
|
110
|
+
*/
|
|
111
|
+
declare function lintGutter(): Extension;
|
|
95
112
|
|
|
96
|
-
export { Action, Diagnostic, closeLintPanel, forceLinting, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics };
|
|
113
|
+
export { Action, Diagnostic, closeLintPanel, diagnosticCount, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Decoration, EditorView, ViewPlugin, logException, WidgetType } from '@codemirror/view';
|
|
2
2
|
import { StateEffect, StateField, Facet } from '@codemirror/state';
|
|
3
|
-
import { hoverTooltip } from '@codemirror/tooltip';
|
|
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 {
|
|
@@ -17,17 +19,18 @@ class LintState {
|
|
|
17
19
|
this.panel = panel;
|
|
18
20
|
this.selected = selected;
|
|
19
21
|
}
|
|
20
|
-
static init(diagnostics, panel) {
|
|
22
|
+
static init(diagnostics, panel, state) {
|
|
21
23
|
let ranges = Decoration.set(diagnostics.map((d) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
diagnostic: d
|
|
26
|
-
}).range(d.from, d.to)
|
|
27
|
-
: Decoration.widget({
|
|
24
|
+
// For zero-length ranges or ranges covering only a line break, create a widget
|
|
25
|
+
return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from)
|
|
26
|
+
? Decoration.widget({
|
|
28
27
|
widget: new DiagnosticWidget(d),
|
|
29
28
|
diagnostic: d
|
|
30
|
-
}).range(d.from)
|
|
29
|
+
}).range(d.from)
|
|
30
|
+
: Decoration.mark({
|
|
31
|
+
attributes: { class: "cm-lintRange cm-lintRange-" + d.severity },
|
|
32
|
+
diagnostic: d
|
|
33
|
+
}).range(d.from, d.to);
|
|
31
34
|
}), true);
|
|
32
35
|
return new LintState(ranges, panel, findDiagnostic(ranges));
|
|
33
36
|
}
|
|
@@ -42,9 +45,9 @@ function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
|
|
|
42
45
|
});
|
|
43
46
|
return found;
|
|
44
47
|
}
|
|
45
|
-
function maybeEnableLint(state, effects
|
|
48
|
+
function maybeEnableLint(state, effects) {
|
|
46
49
|
return state.field(lintState, false) ? effects : effects.concat(StateEffect.appendConfig.of([
|
|
47
|
-
lintState
|
|
50
|
+
lintState,
|
|
48
51
|
EditorView.decorations.compute([lintState], state => {
|
|
49
52
|
let { selected, panel } = state.field(lintState);
|
|
50
53
|
return !selected || !panel || selected.from == selected.to ? Decoration.none : Decoration.set([
|
|
@@ -57,13 +60,18 @@ function maybeEnableLint(state, effects, getState) {
|
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
59
62
|
Returns a transaction spec which updates the current set of
|
|
60
|
-
diagnostics
|
|
63
|
+
diagnostics, and enables the lint extension if if wasn't already
|
|
64
|
+
active.
|
|
61
65
|
*/
|
|
62
66
|
function setDiagnostics(state, diagnostics) {
|
|
63
67
|
return {
|
|
64
|
-
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)]
|
|
68
|
+
effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)])
|
|
65
69
|
};
|
|
66
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
|
+
*/
|
|
67
75
|
const setDiagnosticsEffect = /*@__PURE__*/StateEffect.define();
|
|
68
76
|
const togglePanel = /*@__PURE__*/StateEffect.define();
|
|
69
77
|
const movePanelSelection = /*@__PURE__*/StateEffect.define();
|
|
@@ -82,7 +90,7 @@ const lintState = /*@__PURE__*/StateField.define({
|
|
|
82
90
|
}
|
|
83
91
|
for (let effect of tr.effects) {
|
|
84
92
|
if (effect.is(setDiagnosticsEffect)) {
|
|
85
|
-
value = LintState.init(effect.value, value.panel);
|
|
93
|
+
value = LintState.init(effect.value, value.panel, tr.state);
|
|
86
94
|
}
|
|
87
95
|
else if (effect.is(togglePanel)) {
|
|
88
96
|
value = new LintState(value.diagnostics, effect.value ? LintPanel.open : null, value.selected);
|
|
@@ -96,6 +104,13 @@ const lintState = /*@__PURE__*/StateField.define({
|
|
|
96
104
|
provide: f => [showPanel.from(f, val => val.panel),
|
|
97
105
|
EditorView.decorations.from(f, s => s.diagnostics)]
|
|
98
106
|
});
|
|
107
|
+
/**
|
|
108
|
+
Returns the number of active lint diagnostics in the given state.
|
|
109
|
+
*/
|
|
110
|
+
function diagnosticCount(state) {
|
|
111
|
+
let lint = state.field(lintState, false);
|
|
112
|
+
return lint ? lint.diagnostics.size : 0;
|
|
113
|
+
}
|
|
99
114
|
const activeMark = /*@__PURE__*/Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
|
|
100
115
|
function lintTooltip(view, pos, side) {
|
|
101
116
|
let { diagnostics } = view.state.field(lintState);
|
|
@@ -115,17 +130,20 @@ function lintTooltip(view, pos, side) {
|
|
|
115
130
|
end: stackEnd,
|
|
116
131
|
above: view.state.doc.lineAt(stackStart).to < stackEnd,
|
|
117
132
|
create() {
|
|
118
|
-
return { dom:
|
|
133
|
+
return { dom: diagnosticsTooltip(view, found) };
|
|
119
134
|
}
|
|
120
135
|
};
|
|
121
136
|
}
|
|
137
|
+
function diagnosticsTooltip(view, diagnostics) {
|
|
138
|
+
return elt("ul", { class: "cm-tooltip-lint" }, diagnostics.map(d => renderDiagnostic(view, d, false)));
|
|
139
|
+
}
|
|
122
140
|
/**
|
|
123
141
|
Command to open and focus the lint panel.
|
|
124
142
|
*/
|
|
125
143
|
const openLintPanel = (view) => {
|
|
126
144
|
let field = view.state.field(lintState, false);
|
|
127
145
|
if (!field || !field.panel)
|
|
128
|
-
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]
|
|
146
|
+
view.dispatch({ effects: maybeEnableLint(view.state, [togglePanel.of(true)]) });
|
|
129
147
|
let panel = getPanel(view, LintPanel.open);
|
|
130
148
|
if (panel)
|
|
131
149
|
panel.dom.querySelector(".cm-panel-lint ul").focus();
|
|
@@ -195,12 +213,12 @@ const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
|
195
213
|
}
|
|
196
214
|
}
|
|
197
215
|
update(update) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this.lintTime = Date.now() + delay;
|
|
216
|
+
let source = update.state.facet(lintSource);
|
|
217
|
+
if (update.docChanged || source != update.startState.facet(lintSource)) {
|
|
218
|
+
this.lintTime = Date.now() + source.delay;
|
|
201
219
|
if (!this.set) {
|
|
202
220
|
this.set = true;
|
|
203
|
-
this.timeout = setTimeout(this.run, delay);
|
|
221
|
+
this.timeout = setTimeout(this.run, source.delay);
|
|
204
222
|
}
|
|
205
223
|
}
|
|
206
224
|
}
|
|
@@ -268,6 +286,7 @@ function renderDiagnostic(view, diagnostic, inPanel) {
|
|
|
268
286
|
elt("u", name.slice(keyIndex, keyIndex + 1)),
|
|
269
287
|
name.slice(keyIndex + 1)];
|
|
270
288
|
return elt("button", {
|
|
289
|
+
type: "button",
|
|
271
290
|
class: "cm-diagnosticAction",
|
|
272
291
|
onclick: click,
|
|
273
292
|
onmousedown: click,
|
|
@@ -346,6 +365,7 @@ class LintPanel {
|
|
|
346
365
|
onclick
|
|
347
366
|
});
|
|
348
367
|
this.dom = elt("div", { class: "cm-panel-lint" }, this.list, elt("button", {
|
|
368
|
+
type: "button",
|
|
349
369
|
name: "close",
|
|
350
370
|
"aria-label": this.view.state.phrase("close"),
|
|
351
371
|
onclick: () => closeLintPanel(this.view)
|
|
@@ -460,13 +480,11 @@ class LintPanel {
|
|
|
460
480
|
}
|
|
461
481
|
static open(view) { return new LintPanel(view); }
|
|
462
482
|
}
|
|
483
|
+
function svg(content, attrs = `viewBox="0 0 40 40"`) {
|
|
484
|
+
return `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${encodeURIComponent(content)}</svg>')`;
|
|
485
|
+
}
|
|
463
486
|
function underline(color) {
|
|
464
|
-
|
|
465
|
-
return "none";
|
|
466
|
-
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">
|
|
467
|
-
<path d="m0 3 l2 -2 l1 0 l2 2 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>
|
|
468
|
-
</svg>`;
|
|
469
|
-
return `url('data:image/svg+xml;base64,${btoa(svg)}')`;
|
|
487
|
+
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"`);
|
|
470
488
|
}
|
|
471
489
|
const baseTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
472
490
|
".cm-diagnostic": {
|
|
@@ -493,7 +511,8 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
|
493
511
|
},
|
|
494
512
|
".cm-lintRange": {
|
|
495
513
|
backgroundPosition: "left bottom",
|
|
496
|
-
backgroundRepeat: "repeat-x"
|
|
514
|
+
backgroundRepeat: "repeat-x",
|
|
515
|
+
paddingBottom: "0.7px",
|
|
497
516
|
},
|
|
498
517
|
".cm-lintRange-error": { backgroundImage: /*@__PURE__*/underline("#d11") },
|
|
499
518
|
".cm-lintRange-warning": { backgroundImage: /*@__PURE__*/underline("orange") },
|
|
@@ -552,5 +571,137 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
|
552
571
|
}
|
|
553
572
|
}
|
|
554
573
|
});
|
|
574
|
+
class LintGutterMarker extends GutterMarker {
|
|
575
|
+
constructor(diagnostics) {
|
|
576
|
+
super();
|
|
577
|
+
this.diagnostics = diagnostics;
|
|
578
|
+
this.severity = diagnostics.reduce((max, d) => {
|
|
579
|
+
let s = d.severity;
|
|
580
|
+
return s == "error" || s == "warning" && max == "info" ? s : max;
|
|
581
|
+
}, "info");
|
|
582
|
+
}
|
|
583
|
+
toDOM(view) {
|
|
584
|
+
let elt = document.createElement("div");
|
|
585
|
+
elt.className = "cm-lint-marker cm-lint-marker-" + this.severity;
|
|
586
|
+
elt.onmouseover = () => gutterMarkerMouseOver(view, elt, this.diagnostics);
|
|
587
|
+
return elt;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function trackHoverOn(view, marker) {
|
|
591
|
+
let mousemove = (event) => {
|
|
592
|
+
let rect = marker.getBoundingClientRect();
|
|
593
|
+
if (event.clientX > rect.left - 10 /* Margin */ && event.clientX < rect.right + 10 /* Margin */ &&
|
|
594
|
+
event.clientY > rect.top - 10 /* Margin */ && event.clientY < rect.bottom + 10 /* Margin */)
|
|
595
|
+
return;
|
|
596
|
+
for (let target = event.target; target; target = target.parentNode) {
|
|
597
|
+
if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint"))
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
window.removeEventListener("mousemove", mousemove);
|
|
601
|
+
if (view.state.field(lintGutterTooltip))
|
|
602
|
+
view.dispatch({ effects: setLintGutterTooltip.of(null) });
|
|
603
|
+
};
|
|
604
|
+
window.addEventListener("mousemove", mousemove);
|
|
605
|
+
}
|
|
606
|
+
function gutterMarkerMouseOver(view, marker, diagnostics) {
|
|
607
|
+
function hovered() {
|
|
608
|
+
let line = view.visualLineAtHeight(marker.getBoundingClientRect().top + 5);
|
|
609
|
+
const linePos = view.coordsAtPos(line.from), markerRect = marker.getBoundingClientRect();
|
|
610
|
+
if (linePos) {
|
|
611
|
+
view.dispatch({ effects: setLintGutterTooltip.of({
|
|
612
|
+
pos: line.from,
|
|
613
|
+
above: false,
|
|
614
|
+
create() {
|
|
615
|
+
return {
|
|
616
|
+
dom: diagnosticsTooltip(view, diagnostics),
|
|
617
|
+
offset: { x: markerRect.left - linePos.left, y: 0 }
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}) });
|
|
621
|
+
}
|
|
622
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
623
|
+
trackHoverOn(view, marker);
|
|
624
|
+
}
|
|
625
|
+
let hoverTimeout = setTimeout(hovered, 600 /* Time */);
|
|
626
|
+
marker.onmouseout = () => {
|
|
627
|
+
clearTimeout(hoverTimeout);
|
|
628
|
+
marker.onmouseout = marker.onmousemove = null;
|
|
629
|
+
};
|
|
630
|
+
marker.onmousemove = () => {
|
|
631
|
+
clearTimeout(hoverTimeout);
|
|
632
|
+
hoverTimeout = setTimeout(hovered, 600 /* Time */);
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function markersForDiagnostics(doc, diagnostics) {
|
|
636
|
+
let byLine = Object.create(null);
|
|
637
|
+
for (let diagnostic of diagnostics) {
|
|
638
|
+
let line = doc.lineAt(diagnostic.from);
|
|
639
|
+
(byLine[line.from] || (byLine[line.from] = [])).push(diagnostic);
|
|
640
|
+
}
|
|
641
|
+
let markers = [];
|
|
642
|
+
for (let line in byLine) {
|
|
643
|
+
markers.push(new LintGutterMarker(byLine[line]).range(+line));
|
|
644
|
+
}
|
|
645
|
+
return RangeSet.of(markers, true);
|
|
646
|
+
}
|
|
647
|
+
const lintGutterExtension = /*@__PURE__*/gutter({
|
|
648
|
+
class: "cm-gutter-lint",
|
|
649
|
+
markers: view => view.state.field(lintGutterMarkers),
|
|
650
|
+
});
|
|
651
|
+
const lintGutterMarkers = /*@__PURE__*/StateField.define({
|
|
652
|
+
create() {
|
|
653
|
+
return RangeSet.empty;
|
|
654
|
+
},
|
|
655
|
+
update(markers, tr) {
|
|
656
|
+
markers = markers.map(tr.changes);
|
|
657
|
+
for (let effect of tr.effects)
|
|
658
|
+
if (effect.is(setDiagnosticsEffect)) {
|
|
659
|
+
markers = markersForDiagnostics(tr.state.doc, effect.value);
|
|
660
|
+
}
|
|
661
|
+
return markers;
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
const setLintGutterTooltip = /*@__PURE__*/StateEffect.define();
|
|
665
|
+
const lintGutterTooltip = /*@__PURE__*/StateField.define({
|
|
666
|
+
create() { return null; },
|
|
667
|
+
update(tooltip, tr) {
|
|
668
|
+
if (tooltip && tr.docChanged)
|
|
669
|
+
tooltip = Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) });
|
|
670
|
+
return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip);
|
|
671
|
+
},
|
|
672
|
+
provide: field => showTooltip.from(field)
|
|
673
|
+
});
|
|
674
|
+
const lintGutterTheme = /*@__PURE__*/EditorView.baseTheme({
|
|
675
|
+
".cm-gutter-lint": {
|
|
676
|
+
width: "1.4em",
|
|
677
|
+
"& .cm-gutterElement": {
|
|
678
|
+
padding: "0 .2em",
|
|
679
|
+
display: "flex",
|
|
680
|
+
flexDirection: "column",
|
|
681
|
+
justifyContent: "center",
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
".cm-lint-marker": {
|
|
685
|
+
width: "1em",
|
|
686
|
+
height: "1em",
|
|
687
|
+
},
|
|
688
|
+
".cm-lint-marker-info": {
|
|
689
|
+
content: /*@__PURE__*/svg(`<path fill="#aaf" stroke="#77e" stroke-width="6" stroke-linejoin="round" d="M5 5L35 5L35 35L5 35Z"/>`)
|
|
690
|
+
},
|
|
691
|
+
".cm-lint-marker-warning": {
|
|
692
|
+
content: /*@__PURE__*/svg(`<path fill="#fe8" stroke="#fd7" stroke-width="6" stroke-linejoin="round" d="M20 6L37 35L3 35Z"/>`),
|
|
693
|
+
},
|
|
694
|
+
".cm-lint-marker-error:before": {
|
|
695
|
+
content: /*@__PURE__*/svg(`<circle cx="20" cy="20" r="15" fill="#f87" stroke="#f43" stroke-width="6"/>`)
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
/**
|
|
699
|
+
Returns an extension that installs a gutter showing markers for
|
|
700
|
+
each line that has diagnostics, which can be hovered over to see
|
|
701
|
+
the diagnostics.
|
|
702
|
+
*/
|
|
703
|
+
function lintGutter() {
|
|
704
|
+
return [lintGutterMarkers, lintGutterExtension, lintGutterTheme, lintGutterTooltip];
|
|
705
|
+
}
|
|
555
706
|
|
|
556
|
-
export { closeLintPanel, forceLinting, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics };
|
|
707
|
+
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.
|
|
3
|
+
"version": "0.19.3",
|
|
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/
|
|
30
|
-
"@codemirror/
|
|
31
|
-
"@codemirror/
|
|
32
|
-
"@codemirror/
|
|
29
|
+
"@codemirror/gutter": "^0.19.4",
|
|
30
|
+
"@codemirror/panel": "^0.19.0",
|
|
31
|
+
"@codemirror/rangeset": "^0.19.1",
|
|
32
|
+
"@codemirror/state": "^0.19.4",
|
|
33
|
+
"@codemirror/tooltip": "^0.19.5",
|
|
34
|
+
"@codemirror/view": "^0.19.0",
|
|
33
35
|
"crelt": "^1.0.5"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|