You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
9.8KB

  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. (function(mod) {
  4. if (typeof exports == "object" && typeof module == "object") // CommonJS
  5. mod(require("../../lib/codemirror"));
  6. else if (typeof define == "function" && define.amd) // AMD
  7. define(["../../lib/codemirror"], mod);
  8. else // Plain browser env
  9. mod(CodeMirror);
  10. })(function(CodeMirror) {
  11. "use strict";
  12. var GUTTER_ID = "CodeMirror-lint-markers";
  13. var LINT_LINE_ID = "CodeMirror-lint-line-";
  14. function showTooltip(cm, e, content) {
  15. var tt = document.createElement("div");
  16. tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
  17. tt.appendChild(content.cloneNode(true));
  18. if (cm.state.lint.options.selfContain)
  19. cm.getWrapperElement().appendChild(tt);
  20. else
  21. document.body.appendChild(tt);
  22. function position(e) {
  23. if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
  24. tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
  25. tt.style.left = (e.clientX + 5) + "px";
  26. }
  27. CodeMirror.on(document, "mousemove", position);
  28. position(e);
  29. if (tt.style.opacity != null) tt.style.opacity = 1;
  30. return tt;
  31. }
  32. function rm(elt) {
  33. if (elt.parentNode) elt.parentNode.removeChild(elt);
  34. }
  35. function hideTooltip(tt) {
  36. if (!tt.parentNode) return;
  37. if (tt.style.opacity == null) rm(tt);
  38. tt.style.opacity = 0;
  39. setTimeout(function() { rm(tt); }, 600);
  40. }
  41. function showTooltipFor(cm, e, content, node) {
  42. var tooltip = showTooltip(cm, e, content);
  43. function hide() {
  44. CodeMirror.off(node, "mouseout", hide);
  45. if (tooltip) { hideTooltip(tooltip); tooltip = null; }
  46. }
  47. var poll = setInterval(function() {
  48. if (tooltip) for (var n = node;; n = n.parentNode) {
  49. if (n && n.nodeType == 11) n = n.host;
  50. if (n == document.body) return;
  51. if (!n) { hide(); break; }
  52. }
  53. if (!tooltip) return clearInterval(poll);
  54. }, 400);
  55. CodeMirror.on(node, "mouseout", hide);
  56. }
  57. function LintState(cm, conf, hasGutter) {
  58. this.marked = [];
  59. if (conf instanceof Function) conf = {getAnnotations: conf};
  60. if (!conf || conf === true) conf = {};
  61. this.options = {};
  62. this.linterOptions = conf.options || {};
  63. for (var prop in defaults) this.options[prop] = defaults[prop];
  64. for (var prop in conf) {
  65. if (defaults.hasOwnProperty(prop)) {
  66. if (conf[prop] != null) this.options[prop] = conf[prop];
  67. } else if (!conf.options) {
  68. this.linterOptions[prop] = conf[prop];
  69. }
  70. }
  71. this.timeout = null;
  72. this.hasGutter = hasGutter;
  73. this.onMouseOver = function(e) { onMouseOver(cm, e); };
  74. this.waitingFor = 0
  75. }
  76. var defaults = {
  77. highlightLines: false,
  78. tooltips: true,
  79. delay: 500,
  80. lintOnChange: true,
  81. getAnnotations: null,
  82. async: false,
  83. selfContain: null,
  84. formatAnnotation: null,
  85. onUpdateLinting: null
  86. }
  87. function clearMarks(cm) {
  88. var state = cm.state.lint;
  89. if (state.hasGutter) cm.clearGutter(GUTTER_ID);
  90. if (state.options.highlightLines) clearErrorLines(cm);
  91. for (var i = 0; i < state.marked.length; ++i)
  92. state.marked[i].clear();
  93. state.marked.length = 0;
  94. }
  95. function clearErrorLines(cm) {
  96. cm.eachLine(function(line) {
  97. var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass);
  98. if (has) cm.removeLineClass(line, "wrap", has[0]);
  99. })
  100. }
  101. function makeMarker(cm, labels, severity, multiple, tooltips) {
  102. var marker = document.createElement("div"), inner = marker;
  103. marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity;
  104. if (multiple) {
  105. inner = marker.appendChild(document.createElement("div"));
  106. inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
  107. }
  108. if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
  109. showTooltipFor(cm, e, labels, inner);
  110. });
  111. return marker;
  112. }
  113. function getMaxSeverity(a, b) {
  114. if (a == "error") return a;
  115. else return b;
  116. }
  117. function groupByLine(annotations) {
  118. var lines = [];
  119. for (var i = 0; i < annotations.length; ++i) {
  120. var ann = annotations[i], line = ann.from.line;
  121. (lines[line] || (lines[line] = [])).push(ann);
  122. }
  123. return lines;
  124. }
  125. function annotationTooltip(ann) {
  126. var severity = ann.severity;
  127. if (!severity) severity = "error";
  128. var tip = document.createElement("div");
  129. tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity;
  130. if (typeof ann.messageHTML != 'undefined') {
  131. tip.innerHTML = ann.messageHTML;
  132. } else {
  133. tip.appendChild(document.createTextNode(ann.message));
  134. }
  135. return tip;
  136. }
  137. function lintAsync(cm, getAnnotations) {
  138. var state = cm.state.lint
  139. var id = ++state.waitingFor
  140. function abort() {
  141. id = -1
  142. cm.off("change", abort)
  143. }
  144. cm.on("change", abort)
  145. getAnnotations(cm.getValue(), function(annotations, arg2) {
  146. cm.off("change", abort)
  147. if (state.waitingFor != id) return
  148. if (arg2 && annotations instanceof CodeMirror) annotations = arg2
  149. cm.operation(function() {updateLinting(cm, annotations)})
  150. }, state.linterOptions, cm);
  151. }
  152. function startLinting(cm) {
  153. var state = cm.state.lint;
  154. if (!state) return;
  155. var options = state.options;
  156. /*
  157. * Passing rules in `options` property prevents JSHint (and other linters) from complaining
  158. * about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
  159. */
  160. var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
  161. if (!getAnnotations) return;
  162. if (options.async || getAnnotations.async) {
  163. lintAsync(cm, getAnnotations)
  164. } else {
  165. var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm);
  166. if (!annotations) return;
  167. if (annotations.then) annotations.then(function(issues) {
  168. cm.operation(function() {updateLinting(cm, issues)})
  169. });
  170. else cm.operation(function() {updateLinting(cm, annotations)})
  171. }
  172. }
  173. function updateLinting(cm, annotationsNotSorted) {
  174. var state = cm.state.lint;
  175. if (!state) return;
  176. var options = state.options;
  177. clearMarks(cm);
  178. var annotations = groupByLine(annotationsNotSorted);
  179. for (var line = 0; line < annotations.length; ++line) {
  180. var anns = annotations[line];
  181. if (!anns) continue;
  182. // filter out duplicate messages
  183. var message = [];
  184. anns = anns.filter(function(item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) });
  185. var maxSeverity = null;
  186. var tipLabel = state.hasGutter && document.createDocumentFragment();
  187. for (var i = 0; i < anns.length; ++i) {
  188. var ann = anns[i];
  189. var severity = ann.severity;
  190. if (!severity) severity = "error";
  191. maxSeverity = getMaxSeverity(maxSeverity, severity);
  192. if (options.formatAnnotation) ann = options.formatAnnotation(ann);
  193. if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
  194. if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
  195. className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity,
  196. __annotation: ann
  197. }));
  198. }
  199. // use original annotations[line] to show multiple messages
  200. if (state.hasGutter)
  201. cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1,
  202. options.tooltips));
  203. if (options.highlightLines)
  204. cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity);
  205. }
  206. if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
  207. }
  208. function onChange(cm) {
  209. var state = cm.state.lint;
  210. if (!state) return;
  211. clearTimeout(state.timeout);
  212. state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay);
  213. }
  214. function popupTooltips(cm, annotations, e) {
  215. var target = e.target || e.srcElement;
  216. var tooltip = document.createDocumentFragment();
  217. for (var i = 0; i < annotations.length; i++) {
  218. var ann = annotations[i];
  219. tooltip.appendChild(annotationTooltip(ann));
  220. }
  221. showTooltipFor(cm, e, tooltip, target);
  222. }
  223. function onMouseOver(cm, e) {
  224. var target = e.target || e.srcElement;
  225. if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
  226. var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
  227. var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
  228. var annotations = [];
  229. for (var i = 0; i < spans.length; ++i) {
  230. var ann = spans[i].__annotation;
  231. if (ann) annotations.push(ann);
  232. }
  233. if (annotations.length) popupTooltips(cm, annotations, e);
  234. }
  235. CodeMirror.defineOption("lint", false, function(cm, val, old) {
  236. if (old && old != CodeMirror.Init) {
  237. clearMarks(cm);
  238. if (cm.state.lint.options.lintOnChange !== false)
  239. cm.off("change", onChange);
  240. CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
  241. clearTimeout(cm.state.lint.timeout);
  242. delete cm.state.lint;
  243. }
  244. if (val) {
  245. var gutters = cm.getOption("gutters"), hasLintGutter = false;
  246. for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
  247. var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
  248. if (state.options.lintOnChange)
  249. cm.on("change", onChange);
  250. if (state.options.tooltips != false && state.options.tooltips != "gutter")
  251. CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
  252. startLinting(cm);
  253. }
  254. });
  255. CodeMirror.defineExtension("performLint", function() {
  256. startLinting(this);
  257. });
  258. });