1 CodeMirror
.validate
= (function() {
2 var GUTTER_ID
= "CodeMirror-lint-markers";
3 var SEVERITIES
= /^(?:error|warning)$/;
5 function showTooltip(e
, content
) {
6 var tt
= document
.createElement("div");
7 tt
.className
= "CodeMirror-lint-tooltip";
8 tt
.appendChild(content
.cloneNode(true));
9 document
.body
.appendChild(tt
);
11 function position(e
) {
12 if (!tt
.parentNode
) return CodeMirror
.off(document
, "mousemove", position
);
13 tt
.style
.top
= Math
.max(0, e
.clientY
- tt
.offsetHeight
- 5) + "px";
14 tt
.style
.left
= (e
.clientX
+ 5) + "px";
16 CodeMirror
.on(document
, "mousemove", position
);
18 if (tt
.style
.opacity
!= null) tt
.style
.opacity
= 1;
22 if (elt
.parentNode
) elt
.parentNode
.removeChild(elt
);
24 function hideTooltip(tt
) {
25 if (!tt
.parentNode
) return;
26 if (tt
.style
.opacity
== null) rm(tt
);
28 setTimeout(function() { rm(tt
); }, 600);
31 function showTooltipFor(e
, content
, node
) {
32 var tooltip
= showTooltip(e
, content
);
34 CodeMirror
.off(node
, "mouseout", hide
);
35 if (tooltip
) { hideTooltip(tooltip
); tooltip
= null; }
37 var poll
= setInterval(function() {
38 if (tooltip
) for (var n
= node
;; n
= n
.parentNode
) {
39 if (n
== document
.body
) return;
40 if (!n
) { hide(); break; }
42 if (!tooltip
) return clearInterval(poll
);
44 CodeMirror
.on(node
, "mouseout", hide
);
47 function LintState(cm
, options
, hasGutter
) {
49 this.options
= options
;
51 this.hasGutter
= hasGutter
;
52 this.onMouseOver = function(e
) { onMouseOver(cm
, e
); };
55 function parseOptions(options
) {
56 if (options
instanceof Function
) return {getAnnotations
: options
};
57 else if (!options
|| !options
.getAnnotations
) throw new Error("Required option 'getAnnotations' missing (lint addon)");
61 function clearMarks(cm
) {
62 var state
= cm
.state
.lint
;
63 if (state
.hasGutter
) cm
.clearGutter(GUTTER_ID
);
64 for (var i
= 0; i
< state
.marked
.length
; ++i
)
65 state
.marked
[i
].clear();
66 state
.marked
.length
= 0;
69 function makeMarker(labels
, severity
, multiple
, tooltips
) {
70 var marker
= document
.createElement("div"), inner
= marker
;
71 marker
.className
= "CodeMirror-lint-marker-" + severity
;
73 inner
= marker
.appendChild(document
.createElement("div"));
74 inner
.className
= "CodeMirror-lint-marker-multiple";
77 if (tooltips
!= false) CodeMirror
.on(inner
, "mouseover", function(e
) {
78 showTooltipFor(e
, labels
, inner
);
84 function getMaxSeverity(a
, b
) {
85 if (a
== "error") return a
;
89 function groupByLine(annotations
) {
91 for (var i
= 0; i
< annotations
.length
; ++i
) {
92 var ann
= annotations
[i
], line
= ann
.from.line
;
93 (lines
[line
] || (lines
[line
] = [])).push(ann
);
98 function annotationTooltip(ann
) {
99 var severity
= ann
.severity
;
100 if (!SEVERITIES
.test(severity
)) severity
= "error";
101 var tip
= document
.createElement("div");
102 tip
.className
= "CodeMirror-lint-message-" + severity
;
103 tip
.appendChild(document
.createTextNode(ann
.message
));
107 function startLinting(cm
) {
108 var state
= cm
.state
.lint
, options
= state
.options
;
110 options
.getAnnotations(cm
, updateLinting
, options
);
112 updateLinting(cm
, options
.getAnnotations(cm
.getValue()));
115 function updateLinting(cm
, annotationsNotSorted
) {
117 var state
= cm
.state
.lint
, options
= state
.options
;
119 var annotations
= groupByLine(annotationsNotSorted
);
121 for (var line
= 0; line
< annotations
.length
; ++line
) {
122 var anns
= annotations
[line
];
125 var maxSeverity
= null;
126 var tipLabel
= state
.hasGutter
&& document
.createDocumentFragment();
128 for (var i
= 0; i
< anns
.length
; ++i
) {
130 var severity
= ann
.severity
;
131 if (!SEVERITIES
.test(severity
)) severity
= "error";
132 maxSeverity
= getMaxSeverity(maxSeverity
, severity
);
134 if (options
.formatAnnotation
) ann
= options
.formatAnnotation(ann
);
135 if (state
.hasGutter
) tipLabel
.appendChild(annotationTooltip(ann
));
137 if (ann
.to
) state
.marked
.push(cm
.markText(ann
.from, ann
.to
, {
138 className
: "CodeMirror-lint-mark-" + severity
,
144 cm
.setGutterMarker(line
, GUTTER_ID
, makeMarker(tipLabel
, maxSeverity
, anns
.length
> 1,
145 state
.options
.tooltips
));
147 if (options
.onUpdateLinting
) options
.onUpdateLinting(annotationsNotSorted
, annotations
, cm
);
150 function onChange(cm
) {
151 var state
= cm
.state
.lint
;
152 clearTimeout(state
.timeout
);
153 state
.timeout
= setTimeout(function(){startLinting(cm
);}, state
.options
.delay
|| 500);
156 function popupSpanTooltip(ann
, e
) {
157 var target
= e
.target
|| e
.srcElement
;
158 showTooltipFor(e
, annotationTooltip(ann
), target
);
161 // When the mouseover fires, the cursor might not actually be over
162 // the character itself yet. These pairs of x,y offsets are used to
163 // probe a few nearby points when no suitable marked range is found.
164 var nearby
= [0, 0, 0, 5, 0, -5, 5, 0, -5, 0];
166 function onMouseOver(cm
, e
) {
167 if (!/\bCodeMirror-lint-mark-/.test((e
.target
|| e
.srcElement
).className
)) return;
168 for (var i
= 0; i
< nearby
.length
; i
+= 2) {
169 var spans
= cm
.findMarksAt(cm
.coordsChar({left
: e
.clientX
+ nearby
[i
],
170 top
: e
.clientY
+ nearby
[i
+ 1]}));
171 for (var j
= 0; j
< spans
.length
; ++j
) {
172 var span
= spans
[j
], ann
= span
.__annotation
;
173 if (ann
) return popupSpanTooltip(ann
, e
);
178 CodeMirror
.defineOption("lintWith", false, function(cm
, val
, old
) {
179 if (old
&& old
!= CodeMirror
.Init
) {
181 cm
.off("change", onChange
);
182 CodeMirror
.off(cm
.getWrapperElement(), "mouseover", cm
.state
.lint
.onMouseOver
);
183 delete cm
.state
.lint
;
187 var gutters
= cm
.getOption("gutters"), hasLintGutter
= false;
188 for (var i
= 0; i
< gutters
.length
; ++i
) if (gutters
[i
] == GUTTER_ID
) hasLintGutter
= true;
189 var state
= cm
.state
.lint
= new LintState(cm
, parseOptions(val
), hasLintGutter
);
190 cm
.on("change", onChange
);
191 if (state
.options
.tooltips
!= false)
192 CodeMirror
.on(cm
.getWrapperElement(), "mouseover", state
.onMouseOver
);