'use strict'; var view = require('@codemirror/view'); var state = require('@codemirror/state'); var elt = require('crelt'); class SelectedDiagnostic { constructor(from, to, diagnostic) { this.from = from; this.to = to; this.diagnostic = diagnostic; } } class LintState { constructor(diagnostics, panel, selected) { this.diagnostics = diagnostics; this.panel = panel; this.selected = selected; } static init(diagnostics, panel, state) { // Filter the list of diagnostics for which to create markers let markedDiagnostics = diagnostics; let diagnosticFilter = state.facet(lintConfig).markerFilter; if (diagnosticFilter) markedDiagnostics = diagnosticFilter(markedDiagnostics, state); let ranges = view.Decoration.set(markedDiagnostics.map((d) => { // For zero-length ranges or ranges covering only a line break, create a widget return d.from == d.to || (d.from == d.to - 1 && state.doc.lineAt(d.from).to == d.from) ? view.Decoration.widget({ widget: new DiagnosticWidget(d), diagnostic: d }).range(d.from) : view.Decoration.mark({ attributes: { class: "cm-lintRange cm-lintRange-" + d.severity + (d.markClass ? " " + d.markClass : "") }, diagnostic: d, inclusive: true }).range(d.from, d.to); }), true); return new LintState(ranges, panel, findDiagnostic(ranges)); } } function findDiagnostic(diagnostics, diagnostic = null, after = 0) { let found = null; diagnostics.between(after, 1e9, (from, to, { spec }) => { if (diagnostic && spec.diagnostic != diagnostic) return; found = new SelectedDiagnostic(from, to, spec.diagnostic); return false; }); return found; } function hideTooltip(tr, tooltip) { let from = tooltip.pos, to = tooltip.end || from; let result = tr.state.facet(lintConfig).hideOn(tr, from, to); if (result != null) return result; let line = tr.startState.doc.lineAt(tooltip.pos); return !!(tr.effects.some(e => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(line.from, Math.max(line.to, to))); } function maybeEnableLint(state$1, effects) { return state$1.field(lintState, false) ? effects : effects.concat(state.StateEffect.appendConfig.of(lintExtensions)); } /** Returns a transaction spec which updates the current set of diagnostics, and enables the lint extension if if wasn't already active. */ function setDiagnostics(state, diagnostics) { return { effects: maybeEnableLint(state, [setDiagnosticsEffect.of(diagnostics)]) }; } /** The state effect that updates the set of active diagnostics. Can be useful when writing an extension that needs to track these. */ const setDiagnosticsEffect = state.StateEffect.define(); const togglePanel = state.StateEffect.define(); const movePanelSelection = state.StateEffect.define(); const lintState = state.StateField.define({ create() { return new LintState(view.Decoration.none, null, null); }, update(value, tr) { if (tr.docChanged) { let mapped = value.diagnostics.map(tr.changes), selected = null; if (value.selected) { let selPos = tr.changes.mapPos(value.selected.from, 1); selected = findDiagnostic(mapped, value.selected.diagnostic, selPos) || findDiagnostic(mapped, null, selPos); } value = new LintState(mapped, value.panel, selected); } for (let effect of tr.effects) { if (effect.is(setDiagnosticsEffect)) { value = LintState.init(effect.value, value.panel, tr.state); } else if (effect.is(togglePanel)) { value = new LintState(value.diagnostics, effect.value ? LintPanel.open : null, value.selected); } else if (effect.is(movePanelSelection)) { value = new LintState(value.diagnostics, value.panel, effect.value); } } return value; }, provide: f => [view.showPanel.from(f, val => val.panel), view.EditorView.decorations.from(f, s => s.diagnostics)] }); /** Returns the number of active lint diagnostics in the given state. */ function diagnosticCount(state) { let lint = state.field(lintState, false); return lint ? lint.diagnostics.size : 0; } const activeMark = view.Decoration.mark({ class: "cm-lintRange cm-lintRange-active", inclusive: true }); function lintTooltip(view, pos, side) { let { diagnostics } = view.state.field(lintState); let found = [], stackStart = 2e8, stackEnd = 0; diagnostics.between(pos - (side < 0 ? 1 : 0), pos + (side > 0 ? 1 : 0), (from, to, { spec }) => { if (pos >= from && pos <= to && (from == to || ((pos > from || side > 0) && (pos < to || side < 0)))) { found.push(spec.diagnostic); stackStart = Math.min(from, stackStart); stackEnd = Math.max(to, stackEnd); } }); let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter; if (diagnosticFilter) found = diagnosticFilter(found, view.state); if (!found.length) return null; return { pos: stackStart, end: stackEnd, above: view.state.doc.lineAt(stackStart).to < stackEnd, create() { return { dom: diagnosticsTooltip(view, found) }; } }; } function diagnosticsTooltip(view, diagnostics) { return elt("ul", { class: "cm-tooltip-lint" }, diagnostics.map(d => renderDiagnostic(view, d, false))); } /** Command to open and focus the lint panel. */ const openLintPanel = (view$1) => { let field = view$1.state.field(lintState, false); if (!field || !field.panel) view$1.dispatch({ effects: maybeEnableLint(view$1.state, [togglePanel.of(true)]) }); let panel = view.getPanel(view$1, LintPanel.open); if (panel) panel.dom.querySelector(".cm-panel-lint ul").focus(); return true; }; /** Command to close the lint panel, when open. */ const closeLintPanel = (view) => { let field = view.state.field(lintState, false); if (!field || !field.panel) return false; view.dispatch({ effects: togglePanel.of(false) }); return true; }; /** Move the selection to the next diagnostic. */ const nextDiagnostic = (view) => { let field = view.state.field(lintState, false); if (!field) return false; let sel = view.state.selection.main, next = field.diagnostics.iter(sel.to + 1); if (!next.value) { next = field.diagnostics.iter(0); if (!next.value || next.from == sel.from && next.to == sel.to) return false; } view.dispatch({ selection: { anchor: next.from, head: next.to }, scrollIntoView: true }); return true; }; /** Move the selection to the previous diagnostic. */ const previousDiagnostic = (view) => { let { state } = view, field = state.field(lintState, false); if (!field) return false; let sel = state.selection.main; let prevFrom, prevTo, lastFrom, lastTo; field.diagnostics.between(0, state.doc.length, (from, to) => { if (to < sel.to && (prevFrom == null || prevFrom < from)) { prevFrom = from; prevTo = to; } if (lastFrom == null || from > lastFrom) { lastFrom = from; lastTo = to; } }); if (lastFrom == null || prevFrom == null && lastFrom == sel.from) return false; view.dispatch({ selection: { anchor: prevFrom !== null && prevFrom !== void 0 ? prevFrom : lastFrom, head: prevTo !== null && prevTo !== void 0 ? prevTo : lastTo }, scrollIntoView: true }); return true; }; /** A set of default key bindings for the lint functionality. - Ctrl-Shift-m (Cmd-Shift-m on macOS): [`openLintPanel`](https://codemirror.net/6/docs/ref/#lint.openLintPanel) - F8: [`nextDiagnostic`](https://codemirror.net/6/docs/ref/#lint.nextDiagnostic) */ const lintKeymap = [ { key: "Mod-Shift-m", run: openLintPanel, preventDefault: true }, { key: "F8", run: nextDiagnostic } ]; const lintPlugin = view.ViewPlugin.fromClass(class { constructor(view) { this.view = view; this.timeout = -1; this.set = true; let { delay } = view.state.facet(lintConfig); this.lintTime = Date.now() + delay; this.run = this.run.bind(this); this.timeout = setTimeout(this.run, delay); } run() { clearTimeout(this.timeout); let now = Date.now(); if (now < this.lintTime - 10) { this.timeout = setTimeout(this.run, this.lintTime - now); } else { this.set = false; let { state } = this.view, { sources } = state.facet(lintConfig); if (sources.length) Promise.all(sources.map(source => Promise.resolve(source(this.view)))).then(annotations => { let all = annotations.reduce((a, b) => a.concat(b)); if (this.view.state.doc == state.doc) this.view.dispatch(setDiagnostics(this.view.state, all)); }, error => { view.logException(this.view.state, error); }); } } update(update) { let config = update.state.facet(lintConfig); if (update.docChanged || config != update.startState.facet(lintConfig) || config.needsRefresh && config.needsRefresh(update)) { this.lintTime = Date.now() + config.delay; if (!this.set) { this.set = true; this.timeout = setTimeout(this.run, config.delay); } } } force() { if (this.set) { this.lintTime = Date.now(); this.run(); } } destroy() { clearTimeout(this.timeout); } }); const lintConfig = state.Facet.define({ combine(input) { return Object.assign({ sources: input.map(i => i.source).filter(x => x != null) }, state.combineConfig(input.map(i => i.config), { delay: 750, markerFilter: null, tooltipFilter: null, needsRefresh: null, hideOn: () => null, }, { needsRefresh: (a, b) => !a ? b : !b ? a : u => a(u) || b(u) })); } }); /** Given a diagnostic source, this function returns an extension that enables linting with that source. It will be called whenever the editor is idle (after its content changed). If `null` is given as source, this only configures the lint extension. */ function linter(source, config = {}) { return [ lintConfig.of({ source, config }), lintPlugin, lintExtensions ]; } /** Forces any linters [configured](https://codemirror.net/6/docs/ref/#lint.linter) to run when the editor is idle to run right away. */ function forceLinting(view) { let plugin = view.plugin(lintPlugin); if (plugin) plugin.force(); } function assignKeys(actions) { let assigned = []; if (actions) actions: for (let { name } of actions) { for (let i = 0; i < name.length; i++) { let ch = name[i]; if (/[a-zA-Z]/.test(ch) && !assigned.some(c => c.toLowerCase() == ch.toLowerCase())) { assigned.push(ch); continue actions; } } assigned.push(""); } return assigned; } function renderDiagnostic(view, diagnostic, inPanel) { var _a; let keys = inPanel ? assignKeys(diagnostic.actions) : []; return elt("li", { class: "cm-diagnostic cm-diagnostic-" + diagnostic.severity }, elt("span", { class: "cm-diagnosticText" }, diagnostic.renderMessage ? diagnostic.renderMessage(view) : diagnostic.message), (_a = diagnostic.actions) === null || _a === void 0 ? void 0 : _a.map((action, i) => { let fired = false, click = (e) => { e.preventDefault(); if (fired) return; fired = true; let found = findDiagnostic(view.state.field(lintState).diagnostics, diagnostic); if (found) action.apply(view, found.from, found.to); }; let { name } = action, keyIndex = keys[i] ? name.indexOf(keys[i]) : -1; let nameElt = keyIndex < 0 ? name : [name.slice(0, keyIndex), elt("u", name.slice(keyIndex, keyIndex + 1)), name.slice(keyIndex + 1)]; return elt("button", { type: "button", class: "cm-diagnosticAction", onclick: click, onmousedown: click, "aria-label": ` Action: ${name}${keyIndex < 0 ? "" : ` (access key "${keys[i]})"`}.` }, nameElt); }), diagnostic.source && elt("div", { class: "cm-diagnosticSource" }, diagnostic.source)); } class DiagnosticWidget extends view.WidgetType { constructor(diagnostic) { super(); this.diagnostic = diagnostic; } eq(other) { return other.diagnostic == this.diagnostic; } toDOM() { return elt("span", { class: "cm-lintPoint cm-lintPoint-" + this.diagnostic.severity }); } } class PanelItem { constructor(view, diagnostic) { this.diagnostic = diagnostic; this.id = "item_" + Math.floor(Math.random() * 0xffffffff).toString(16); this.dom = renderDiagnostic(view, diagnostic, true); this.dom.id = this.id; this.dom.setAttribute("role", "option"); } } class LintPanel { constructor(view) { this.view = view; this.items = []; let onkeydown = (event) => { if (event.keyCode == 27) { // Escape closeLintPanel(this.view); this.view.focus(); } else if (event.keyCode == 38 || event.keyCode == 33) { // ArrowUp, PageUp this.moveSelection((this.selectedIndex - 1 + this.items.length) % this.items.length); } else if (event.keyCode == 40 || event.keyCode == 34) { // ArrowDown, PageDown this.moveSelection((this.selectedIndex + 1) % this.items.length); } else if (event.keyCode == 36) { // Home this.moveSelection(0); } else if (event.keyCode == 35) { // End this.moveSelection(this.items.length - 1); } else if (event.keyCode == 13) { // Enter this.view.focus(); } else if (event.keyCode >= 65 && event.keyCode <= 90 && this.selectedIndex >= 0) { // A-Z let { diagnostic } = this.items[this.selectedIndex], keys = assignKeys(diagnostic.actions); for (let i = 0; i < keys.length; i++) if (keys[i].toUpperCase().charCodeAt(0) == event.keyCode) { let found = findDiagnostic(this.view.state.field(lintState).diagnostics, diagnostic); if (found) diagnostic.actions[i].apply(view, found.from, found.to); } } else { return; } event.preventDefault(); }; let onclick = (event) => { for (let i = 0; i < this.items.length; i++) { if (this.items[i].dom.contains(event.target)) this.moveSelection(i); } }; this.list = elt("ul", { tabIndex: 0, role: "listbox", "aria-label": this.view.state.phrase("Diagnostics"), onkeydown, onclick }); this.dom = elt("div", { class: "cm-panel-lint" }, this.list, elt("button", { type: "button", name: "close", "aria-label": this.view.state.phrase("close"), onclick: () => closeLintPanel(this.view) }, "×")); this.update(); } get selectedIndex() { let selected = this.view.state.field(lintState).selected; if (!selected) return -1; for (let i = 0; i < this.items.length; i++) if (this.items[i].diagnostic == selected.diagnostic) return i; return -1; } update() { let { diagnostics, selected } = this.view.state.field(lintState); let i = 0, needsSync = false, newSelectedItem = null; diagnostics.between(0, this.view.state.doc.length, (_start, _end, { spec }) => { let found = -1, item; for (let j = i; j < this.items.length; j++) if (this.items[j].diagnostic == spec.diagnostic) { found = j; break; } if (found < 0) { item = new PanelItem(this.view, spec.diagnostic); this.items.splice(i, 0, item); needsSync = true; } else { item = this.items[found]; if (found > i) { this.items.splice(i, found - i); needsSync = true; } } if (selected && item.diagnostic == selected.diagnostic) { if (!item.dom.hasAttribute("aria-selected")) { item.dom.setAttribute("aria-selected", "true"); newSelectedItem = item; } } else if (item.dom.hasAttribute("aria-selected")) { item.dom.removeAttribute("aria-selected"); } i++; }); while (i < this.items.length && !(this.items.length == 1 && this.items[0].diagnostic.from < 0)) { needsSync = true; this.items.pop(); } if (this.items.length == 0) { this.items.push(new PanelItem(this.view, { from: -1, to: -1, severity: "info", message: this.view.state.phrase("No diagnostics") })); needsSync = true; } if (newSelectedItem) { this.list.setAttribute("aria-activedescendant", newSelectedItem.id); this.view.requestMeasure({ key: this, read: () => ({ sel: newSelectedItem.dom.getBoundingClientRect(), panel: this.list.getBoundingClientRect() }), write: ({ sel, panel }) => { let scaleY = panel.height / this.list.offsetHeight; if (sel.top < panel.top) this.list.scrollTop -= (panel.top - sel.top) / scaleY; else if (sel.bottom > panel.bottom) this.list.scrollTop += (sel.bottom - panel.bottom) / scaleY; } }); } else if (this.selectedIndex < 0) { this.list.removeAttribute("aria-activedescendant"); } if (needsSync) this.sync(); } sync() { let domPos = this.list.firstChild; function rm() { let prev = domPos; domPos = prev.nextSibling; prev.remove(); } for (let item of this.items) { if (item.dom.parentNode == this.list) { while (domPos != item.dom) rm(); domPos = item.dom.nextSibling; } else { this.list.insertBefore(item.dom, domPos); } } while (domPos) rm(); } moveSelection(selectedIndex) { if (this.selectedIndex < 0) return; let field = this.view.state.field(lintState); let selection = findDiagnostic(field.diagnostics, this.items[selectedIndex].diagnostic); if (!selection) return; this.view.dispatch({ selection: { anchor: selection.from, head: selection.to }, scrollIntoView: true, effects: movePanelSelection.of(selection) }); } static open(view) { return new LintPanel(view); } } function svg(content, attrs = `viewBox="0 0 40 40"`) { return `url('data:image/svg+xml,${encodeURIComponent(content)}')`; } function underline(color) { return svg(``, `width="6" height="3"`); } const baseTheme = view.EditorView.baseTheme({ ".cm-diagnostic": { padding: "3px 6px 3px 8px", marginLeft: "-1px", display: "block", whiteSpace: "pre-wrap" }, ".cm-diagnostic-error": { borderLeft: "5px solid #d11" }, ".cm-diagnostic-warning": { borderLeft: "5px solid orange" }, ".cm-diagnostic-info": { borderLeft: "5px solid #999" }, ".cm-diagnostic-hint": { borderLeft: "5px solid #66d" }, ".cm-diagnosticAction": { font: "inherit", border: "none", padding: "2px 4px", backgroundColor: "#444", color: "white", borderRadius: "3px", marginLeft: "8px", cursor: "pointer" }, ".cm-diagnosticSource": { fontSize: "70%", opacity: .7 }, ".cm-lintRange": { backgroundPosition: "left bottom", backgroundRepeat: "repeat-x", paddingBottom: "0.7px", }, ".cm-lintRange-error": { backgroundImage: underline("#d11") }, ".cm-lintRange-warning": { backgroundImage: underline("orange") }, ".cm-lintRange-info": { backgroundImage: underline("#999") }, ".cm-lintRange-hint": { backgroundImage: underline("#66d") }, ".cm-lintRange-active": { backgroundColor: "#ffdd9980" }, ".cm-tooltip-lint": { padding: 0, margin: 0 }, ".cm-lintPoint": { position: "relative", "&:after": { content: '""', position: "absolute", bottom: 0, left: "-2px", borderLeft: "3px solid transparent", borderRight: "3px solid transparent", borderBottom: "4px solid #d11" } }, ".cm-lintPoint-warning": { "&:after": { borderBottomColor: "orange" } }, ".cm-lintPoint-info": { "&:after": { borderBottomColor: "#999" } }, ".cm-lintPoint-hint": { "&:after": { borderBottomColor: "#66d" } }, ".cm-panel.cm-panel-lint": { position: "relative", "& ul": { maxHeight: "100px", overflowY: "auto", "& [aria-selected]": { backgroundColor: "#ddd", "& u": { textDecoration: "underline" } }, "&:focus [aria-selected]": { background_fallback: "#bdf", backgroundColor: "Highlight", color_fallback: "white", color: "HighlightText" }, "& u": { textDecoration: "none" }, padding: 0, margin: 0 }, "& [name=close]": { position: "absolute", top: "0", right: "2px", background: "inherit", border: "none", font: "inherit", padding: 0, margin: 0 } } }); function severityWeight(sev) { return sev == "error" ? 4 : sev == "warning" ? 3 : sev == "info" ? 2 : 1; } class LintGutterMarker extends view.GutterMarker { constructor(diagnostics) { super(); this.diagnostics = diagnostics; this.severity = diagnostics.reduce((max, d) => severityWeight(max) < severityWeight(d.severity) ? d.severity : max, "hint"); } toDOM(view) { let elt = document.createElement("div"); elt.className = "cm-lint-marker cm-lint-marker-" + this.severity; let diagnostics = this.diagnostics; let diagnosticsFilter = view.state.facet(lintGutterConfig).tooltipFilter; if (diagnosticsFilter) diagnostics = diagnosticsFilter(diagnostics, view.state); if (diagnostics.length) elt.onmouseover = () => gutterMarkerMouseOver(view, elt, diagnostics); return elt; } } function trackHoverOn(view, marker) { let mousemove = (event) => { let rect = marker.getBoundingClientRect(); if (event.clientX > rect.left - 10 /* Hover.Margin */ && event.clientX < rect.right + 10 /* Hover.Margin */ && event.clientY > rect.top - 10 /* Hover.Margin */ && event.clientY < rect.bottom + 10 /* Hover.Margin */) return; for (let target = event.target; target; target = target.parentNode) { if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint")) return; } window.removeEventListener("mousemove", mousemove); if (view.state.field(lintGutterTooltip)) view.dispatch({ effects: setLintGutterTooltip.of(null) }); }; window.addEventListener("mousemove", mousemove); } function gutterMarkerMouseOver(view, marker, diagnostics) { function hovered() { let line = view.elementAtHeight(marker.getBoundingClientRect().top + 5 - view.documentTop); const linePos = view.coordsAtPos(line.from); if (linePos) { view.dispatch({ effects: setLintGutterTooltip.of({ pos: line.from, above: false, create() { return { dom: diagnosticsTooltip(view, diagnostics), getCoords: () => marker.getBoundingClientRect() }; } }) }); } marker.onmouseout = marker.onmousemove = null; trackHoverOn(view, marker); } let { hoverTime } = view.state.facet(lintGutterConfig); let hoverTimeout = setTimeout(hovered, hoverTime); marker.onmouseout = () => { clearTimeout(hoverTimeout); marker.onmouseout = marker.onmousemove = null; }; marker.onmousemove = () => { clearTimeout(hoverTimeout); hoverTimeout = setTimeout(hovered, hoverTime); }; } function markersForDiagnostics(doc, diagnostics) { let byLine = Object.create(null); for (let diagnostic of diagnostics) { let line = doc.lineAt(diagnostic.from); (byLine[line.from] || (byLine[line.from] = [])).push(diagnostic); } let markers = []; for (let line in byLine) { markers.push(new LintGutterMarker(byLine[line]).range(+line)); } return state.RangeSet.of(markers, true); } const lintGutterExtension = view.gutter({ class: "cm-gutter-lint", markers: view => view.state.field(lintGutterMarkers), }); const lintGutterMarkers = state.StateField.define({ create() { return state.RangeSet.empty; }, update(markers, tr) { markers = markers.map(tr.changes); let diagnosticFilter = tr.state.facet(lintGutterConfig).markerFilter; for (let effect of tr.effects) { if (effect.is(setDiagnosticsEffect)) { let diagnostics = effect.value; if (diagnosticFilter) diagnostics = diagnosticFilter(diagnostics || [], tr.state); markers = markersForDiagnostics(tr.state.doc, diagnostics.slice(0)); } } return markers; } }); const setLintGutterTooltip = state.StateEffect.define(); const lintGutterTooltip = state.StateField.define({ create() { return null; }, update(tooltip, tr) { if (tooltip && tr.docChanged) tooltip = hideTooltip(tr, tooltip) ? null : Object.assign(Object.assign({}, tooltip), { pos: tr.changes.mapPos(tooltip.pos) }); return tr.effects.reduce((t, e) => e.is(setLintGutterTooltip) ? e.value : t, tooltip); }, provide: field => view.showTooltip.from(field) }); const lintGutterTheme = view.EditorView.baseTheme({ ".cm-gutter-lint": { width: "1.4em", "& .cm-gutterElement": { padding: ".2em" } }, ".cm-lint-marker": { width: "1em", height: "1em" }, ".cm-lint-marker-info": { content: svg(``) }, ".cm-lint-marker-warning": { content: svg(``), }, ".cm-lint-marker-error": { content: svg(``) }, }); const lintExtensions = [ lintState, view.EditorView.decorations.compute([lintState], state => { let { selected, panel } = state.field(lintState); return !selected || !panel || selected.from == selected.to ? view.Decoration.none : view.Decoration.set([ activeMark.range(selected.from, selected.to) ]); }), view.hoverTooltip(lintTooltip, { hideOn: hideTooltip }), baseTheme ]; const lintGutterConfig = state.Facet.define({ combine(configs) { return state.combineConfig(configs, { hoverTime: 300 /* Hover.Time */, markerFilter: null, tooltipFilter: null }); } }); /** Returns an extension that installs a gutter showing markers for each line that has diagnostics, which can be hovered over to see the diagnostics. */ function lintGutter(config = {}) { return [lintGutterConfig.of(config), lintGutterMarkers, lintGutterExtension, lintGutterTheme, lintGutterTooltip]; } /** Iterate over the marked diagnostics for the given editor state, calling `f` for each of them. Note that, if the document changed since the diagnostics were created, the `Diagnostic` object will hold the original outdated position, whereas the `to` and `from` arguments hold the diagnostic's current position. */ function forEachDiagnostic(state$1, f) { let lState = state$1.field(lintState, false); if (lState && lState.diagnostics.size) for (let iter = state.RangeSet.iter([lState.diagnostics]); iter.value; iter.next()) f(iter.value.spec.diagnostic, iter.from, iter.to); } exports.closeLintPanel = closeLintPanel; exports.diagnosticCount = diagnosticCount; exports.forEachDiagnostic = forEachDiagnostic; exports.forceLinting = forceLinting; exports.lintGutter = lintGutter; exports.lintKeymap = lintKeymap; exports.linter = linter; exports.nextDiagnostic = nextDiagnostic; exports.openLintPanel = openLintPanel; exports.previousDiagnostic = previousDiagnostic; exports.setDiagnostics = setDiagnostics; exports.setDiagnosticsEffect = setDiagnosticsEffect;