// CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/LICENSE ;(function (mod) { if (typeof exports == 'object' && typeof module == 'object') // CommonJS mod(require('../../lib/codemirror')) else if (typeof define == 'function' && define.amd) // AMD define(['../../lib/codemirror'], mod) // Plain browser env else mod(CodeMirror) })(function (CodeMirror) { 'use strict' var GUTTER_ID = 'CodeMirror-lint-markers' var LINT_LINE_ID = 'CodeMirror-lint-line-' function showTooltip(cm, e, content) { var tt = document.createElement('div') tt.className = 'CodeMirror-lint-tooltip cm-s-' + cm.options.theme tt.appendChild(content.cloneNode(true)) if (cm.state.lint.options.selfContain) cm.getWrapperElement().appendChild(tt) else document.body.appendChild(tt) function position(e) { if (!tt.parentNode) return CodeMirror.off(document, 'mousemove', position) tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + 'px' tt.style.left = e.clientX + 5 + 'px' } CodeMirror.on(document, 'mousemove', position) position(e) if (tt.style.opacity != null) tt.style.opacity = 1 return tt } function rm(elt) { if (elt.parentNode) elt.parentNode.removeChild(elt) } function hideTooltip(tt) { if (!tt.parentNode) return if (tt.style.opacity == null) rm(tt) tt.style.opacity = 0 setTimeout(function () { rm(tt) }, 600) } function showTooltipFor(cm, e, content, node) { var tooltip = showTooltip(cm, e, content) function hide() { CodeMirror.off(node, 'mouseout', hide) if (tooltip) { hideTooltip(tooltip) tooltip = null } } var poll = setInterval(function () { if (tooltip) for (var n = node; ; n = n.parentNode) { if (n && n.nodeType == 11) n = n.host if (n == document.body) return if (!n) { hide() break } } if (!tooltip) return clearInterval(poll) }, 400) CodeMirror.on(node, 'mouseout', hide) } function LintState(cm, conf, hasGutter) { this.marked = [] if (conf instanceof Function) conf = { getAnnotations: conf } if (!conf || conf === true) conf = {} this.options = {} this.linterOptions = conf.options || {} for (var prop in defaults) this.options[prop] = defaults[prop] for (var prop in conf) { if (defaults.hasOwnProperty(prop)) { if (conf[prop] != null) this.options[prop] = conf[prop] } else if (!conf.options) { this.linterOptions[prop] = conf[prop] } } this.timeout = null this.hasGutter = hasGutter this.onMouseOver = function (e) { onMouseOver(cm, e) } this.waitingFor = 0 } var defaults = { highlightLines: false, tooltips: true, delay: 500, lintOnChange: true, getAnnotations: null, async: false, selfContain: null, formatAnnotation: null, onUpdateLinting: null, } function clearMarks(cm) { var state = cm.state.lint if (state.hasGutter) cm.clearGutter(GUTTER_ID) if (state.options.highlightLines) clearErrorLines(cm) for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear() state.marked.length = 0 } function clearErrorLines(cm) { cm.eachLine(function (line) { var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass) if (has) cm.removeLineClass(line, 'wrap', has[0]) }) } function makeMarker(cm, labels, severity, multiple, tooltips) { var marker = document.createElement('div'), inner = marker marker.className = 'CodeMirror-lint-marker CodeMirror-lint-marker-' + severity if (multiple) { inner = marker.appendChild(document.createElement('div')) inner.className = 'CodeMirror-lint-marker CodeMirror-lint-marker-multiple' } if (tooltips != false) CodeMirror.on(inner, 'mouseover', function (e) { showTooltipFor(cm, e, labels, inner) }) return marker } function getMaxSeverity(a, b) { if (a == 'error') return a else return b } function groupByLine(annotations) { var lines = [] for (var i = 0; i < annotations.length; ++i) { var ann = annotations[i], line = ann.from.line ;(lines[line] || (lines[line] = [])).push(ann) } return lines } function annotationTooltip(ann) { var severity = ann.severity if (!severity) severity = 'error' var tip = document.createElement('div') tip.className = 'CodeMirror-lint-message CodeMirror-lint-message-' + severity if (typeof ann.messageHTML != 'undefined') { tip.innerHTML = ann.messageHTML } else { tip.appendChild(document.createTextNode(ann.message)) } return tip } function lintAsync(cm, getAnnotations) { var state = cm.state.lint var id = ++state.waitingFor function abort() { id = -1 cm.off('change', abort) } cm.on('change', abort) getAnnotations( cm.getValue(), function (annotations, arg2) { cm.off('change', abort) if (state.waitingFor != id) return if (arg2 && annotations instanceof CodeMirror) annotations = arg2 cm.operation(function () { updateLinting(cm, annotations) }) }, state.linterOptions, cm ) } function startLinting(cm) { var state = cm.state.lint if (!state) return var options = state.options /* * Passing rules in `options` property prevents JSHint (and other linters) from complaining * about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc. */ var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), 'lint') if (!getAnnotations) return if (options.async || getAnnotations.async) { lintAsync(cm, getAnnotations) } else { var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm) if (!annotations) return if (annotations.then) annotations.then(function (issues) { cm.operation(function () { updateLinting(cm, issues) }) }) else cm.operation(function () { updateLinting(cm, annotations) }) } } function updateLinting(cm, annotationsNotSorted) { var state = cm.state.lint if (!state) return var options = state.options clearMarks(cm) var annotations = groupByLine(annotationsNotSorted) for (var line = 0; line < annotations.length; ++line) { var anns = annotations[line] if (!anns) continue // filter out duplicate messages var message = [] anns = anns.filter(function (item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) }) var maxSeverity = null var tipLabel = state.hasGutter && document.createDocumentFragment() for (var i = 0; i < anns.length; ++i) { var ann = anns[i] var severity = ann.severity if (!severity) severity = 'error' maxSeverity = getMaxSeverity(maxSeverity, severity) if (options.formatAnnotation) ann = options.formatAnnotation(ann) if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann)) if (ann.to) state.marked.push( cm.markText(ann.from, ann.to, { className: 'CodeMirror-lint-mark CodeMirror-lint-mark-' + severity, __annotation: ann, }) ) } // use original annotations[line] to show multiple messages if (state.hasGutter) cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1, options.tooltips)) if (options.highlightLines) cm.addLineClass(line, 'wrap', LINT_LINE_ID + maxSeverity) } if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm) } function onChange(cm) { var state = cm.state.lint if (!state) return clearTimeout(state.timeout) state.timeout = setTimeout(function () { startLinting(cm) }, state.options.delay) } function popupTooltips(cm, annotations, e) { var target = e.target || e.srcElement var tooltip = document.createDocumentFragment() for (var i = 0; i < annotations.length; i++) { var ann = annotations[i] tooltip.appendChild(annotationTooltip(ann)) } showTooltipFor(cm, e, tooltip, target) } function onMouseOver(cm, e) { var target = e.target || e.srcElement if (!/\bCodeMirror-lint-mark-/.test(target.className)) return var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2 var spans = cm.findMarksAt(cm.coordsChar({ left: x, top: y }, 'client')) var annotations = [] for (var i = 0; i < spans.length; ++i) { var ann = spans[i].__annotation if (ann) annotations.push(ann) } if (annotations.length) popupTooltips(cm, annotations, e) } CodeMirror.defineOption('lint', false, function (cm, val, old) { if (old && old != CodeMirror.Init) { clearMarks(cm) if (cm.state.lint.options.lintOnChange !== false) cm.off('change', onChange) CodeMirror.off(cm.getWrapperElement(), 'mouseover', cm.state.lint.onMouseOver) clearTimeout(cm.state.lint.timeout) delete cm.state.lint } if (val) { var gutters = cm.getOption('gutters'), hasLintGutter = false for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true var state = (cm.state.lint = new LintState(cm, val, hasLintGutter)) if (state.options.lintOnChange) cm.on('change', onChange) if (state.options.tooltips != false && state.options.tooltips != 'gutter') CodeMirror.on(cm.getWrapperElement(), 'mouseover', state.onMouseOver) startLinting(cm) } }) CodeMirror.defineExtension('performLint', function () { startLinting(this) }) })