|
12 | 12 | "use strict"; |
13 | 13 | var GUTTER_ID = "CodeMirror-lint-markers"; |
14 | 14 |
|
15 | | - function showTooltip(cm, e, content) { |
| 15 | + function showTooltip(cm, e, content, node) { |
16 | 16 | var tt = document.createElement("div"); |
17 | 17 | tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme; |
18 | 18 | tt.appendChild(content.cloneNode(true)); |
|
21 | 21 | else |
22 | 22 | document.body.appendChild(tt); |
23 | 23 |
|
24 | | - function position(e) { |
25 | | - if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position); |
26 | | - tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px"; |
27 | | - tt.style.left = (e.clientX + 5) + "px"; |
| 24 | + if (cm.state.lint.options.fixedTooltip) { |
| 25 | + const { top, left } = node.getBoundingClientRect() |
| 26 | + |
| 27 | + tt.style.top = top - 5 + 'px' |
| 28 | + tt.style.left = left + 20 + 'px' |
| 29 | + } else { |
| 30 | + function position(e) { |
| 31 | + if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position); |
| 32 | + tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px"; |
| 33 | + tt.style.left = (e.clientX + 5) + "px"; |
| 34 | + } |
| 35 | + CodeMirror.on(document, "mousemove", position); |
| 36 | + position(e); |
28 | 37 | } |
29 | | - CodeMirror.on(document, "mousemove", position); |
30 | | - position(e); |
| 38 | + |
31 | 39 | if (tt.style.opacity != null) tt.style.opacity = 1; |
32 | 40 | return tt; |
33 | 41 | } |
|
42 | 50 | } |
43 | 51 |
|
44 | 52 | function showTooltipFor(cm, e, content, node) { |
45 | | - var tooltip = showTooltip(cm, e, content); |
| 53 | + var tooltip = showTooltip(cm, e, content, node); |
46 | 54 | function hide() { |
47 | 55 | CodeMirror.off(node, "mouseout", hide); |
48 | 56 | if (tooltip) { hideTooltip(tooltip); tooltip = null; } |
49 | 57 | } |
50 | | - var poll = setInterval(function() { |
51 | | - if (tooltip) for (var n = node;; n = n.parentNode) { |
52 | | - if (n && n.nodeType == 11) n = n.host; |
53 | | - if (n == document.body) return; |
54 | | - if (!n) { hide(); break; } |
55 | | - } |
56 | | - if (!tooltip) return clearInterval(poll); |
57 | | - }, 400); |
| 58 | + |
| 59 | + if (!cm.state.lint.options.fixedTooltip) { |
| 60 | + var poll = setInterval(function() { |
| 61 | + if (tooltip) for (var n = node;; n = n.parentNode) { |
| 62 | + if (n && n.nodeType == 11) n = n.host; |
| 63 | + if (n == document.body) return; |
| 64 | + if (!n) { hide(); break; } |
| 65 | + } |
| 66 | + if (!tooltip) return clearInterval(poll); |
| 67 | + }, 400); |
| 68 | + } |
| 69 | + |
58 | 70 | CodeMirror.on(node, "mouseout", hide); |
59 | 71 | } |
| 72 | + |
| 73 | + /** |
| 74 | + * |
| 75 | + * @param {*} cm |
| 76 | + * @param {Array<{ content: string, html: string, onClick: any }>} menus |
| 77 | + * @param {*} e |
| 78 | + */ |
| 79 | + function showMenu (cm, menus, e) { |
| 80 | + var target = e.target || e.srcElement; |
| 81 | + var state = cm.state.lint |
| 82 | + |
| 83 | + if (state.hints) { |
| 84 | + remove(state.hints) |
| 85 | + } |
| 86 | + |
| 87 | + if (!menus && menus.length > 0) { |
| 88 | + return |
| 89 | + } |
| 90 | + |
| 91 | + // build menu |
| 92 | + var hints = document.createElement("ul"); |
| 93 | + var theme = cm.options.theme; |
| 94 | + hints.className = "CodeMirror-hints " + theme; |
| 95 | + hints.style.position = 'fixed' |
| 96 | + hints.style.zIndex = '999' |
| 97 | + |
| 98 | + for (let item of menus) { |
| 99 | + const elt = hints.appendChild(document.createElement('li')) |
| 100 | + elt.className = 'CodeMirror-hint' |
| 101 | + if (item.content) { |
| 102 | + elt.textContent = item.content |
| 103 | + } else { |
| 104 | + elt.innerHTML = item.html |
| 105 | + } |
| 106 | + const onClick = item.onClick |
| 107 | + elt.addEventListener('click', (e) => { |
| 108 | + if (typeof onClick === 'function') { |
| 109 | + onClick(e) |
| 110 | + } |
| 111 | + remove(hints) |
| 112 | + }) |
| 113 | + } |
| 114 | + |
| 115 | + var removal = function () { setTimeout(function () { remove(hints) }, 100) } |
| 116 | + function remove (hints) { |
| 117 | + if (hints.parentNode) { |
| 118 | + hints.parentNode.removeChild(hints) |
| 119 | + } |
| 120 | + state.hints = null |
| 121 | + |
| 122 | + cm.off("mousedown", removal) |
| 123 | + cm.off("scroll", removal) |
| 124 | + } |
| 125 | + |
| 126 | + state.hints = hints |
| 127 | + document.body.appendChild(hints) |
| 128 | + const { left, top } = target.getBoundingClientRect() |
| 129 | + |
| 130 | + hints.style.top = top + 5 + 'px' |
| 131 | + hints.style.left = left + 20 + 'px' |
| 132 | + |
| 133 | + |
| 134 | + cm.on("mousedown", removal) |
| 135 | + cm.on("scroll", removal) |
| 136 | + } |
60 | 137 |
|
61 | 138 | function LintState(cm, options, hasGutter) { |
62 | 139 | this.marked = []; |
63 | 140 | this.options = options; |
64 | 141 | this.timeout = null; |
65 | 142 | this.hasGutter = hasGutter; |
66 | 143 | this.onMouseOver = function(e) { onMouseOver(cm, e); }; |
| 144 | + this.onClick = function(e) { onClick(cm, e); }; |
67 | 145 | this.waitingFor = 0 |
| 146 | + |
| 147 | + this.contextMenuEnable = typeof options.contextmenu === 'function' |
68 | 148 | } |
69 | 149 |
|
70 | 150 | function parseOptions(_cm, options) { |
|
81 | 161 | state.marked.length = 0; |
82 | 162 | } |
83 | 163 |
|
84 | | - function makeMarker(cm, labels, severity, multiple, tooltips) { |
| 164 | + function makeMarker(cm, labels, severity, multiple, tooltips, annotations) { |
85 | 165 | var marker = document.createElement("div"), inner = marker; |
86 | 166 | marker.className = "CodeMirror-lint-marker-" + severity; |
87 | 167 | if (multiple) { |
|
93 | 173 | showTooltipFor(cm, e, labels, inner); |
94 | 174 | }); |
95 | 175 |
|
| 176 | + if (cm.state.lint.contextMenuEnable) { |
| 177 | + marker.addEventListener('click', function (e) { |
| 178 | + const menus = cm.state.lint.options.contextmenu(annotations) |
| 179 | + showMenu(cm, menus, e) |
| 180 | + }) |
| 181 | + } |
| 182 | + |
96 | 183 | return marker; |
97 | 184 | } |
98 | 185 |
|
|
190 | 277 |
|
191 | 278 | if (state.hasGutter) |
192 | 279 | cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1, |
193 | | - state.options.tooltips)); |
| 280 | + state.options.tooltips, anns)); |
194 | 281 | } |
195 | 282 | if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm); |
196 | 283 | } |
|
211 | 298 | } |
212 | 299 | showTooltipFor(cm, e, tooltip, target); |
213 | 300 | } |
214 | | - |
215 | | - function onMouseOver(cm, e) { |
| 301 | + |
| 302 | + function handleMarkerAction (cm, e, cb) { |
216 | 303 | var target = e.target || e.srcElement; |
217 | 304 | if (!/\bCodeMirror-lint-mark-/.test(target.className)) return; |
218 | 305 | var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2; |
|
223 | 310 | var ann = spans[i].__annotation; |
224 | 311 | if (ann) annotations.push(ann); |
225 | 312 | } |
226 | | - if (annotations.length) popupTooltips(cm, annotations, e); |
| 313 | + |
| 314 | + if (annotations.length) cb(cm, annotations, e) |
| 315 | + } |
| 316 | + |
| 317 | + function onMouseOver(cm, e) { |
| 318 | + handleMarkerAction(cm, e, popupTooltips) |
| 319 | + } |
| 320 | + |
| 321 | + function onClick (cm, e) { |
| 322 | + handleMarkerAction(cm, e, function (cm, annotations, e) { |
| 323 | + const menus = cm.state.lint.options.contextmenu(annotations) |
| 324 | + showMenu(cm, menus, e) |
| 325 | + }) |
227 | 326 | } |
228 | 327 |
|
229 | 328 | CodeMirror.defineOption("lint", false, function(cm, val, old) { |
|
244 | 343 | cm.on("change", onChange); |
245 | 344 | if (state.options.tooltips != false && state.options.tooltips != "gutter") |
246 | 345 | CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); |
| 346 | + if (state.contextMenuEnable) { |
| 347 | + CodeMirror.on(cm.getWrapperElement(), "click", state.onClick); |
| 348 | + } |
247 | 349 |
|
248 | 350 | startLinting(cm); |
249 | 351 | } |
|
0 commit comments