stylus/edit/match-highlighter-helper.js

100 lines
3.2 KiB
JavaScript

/* global CodeMirror */
'use strict';
(() => {
const HL_APPROVED = 'cm-matchhighlight-approved';
const originalAddOverlay = CodeMirror.prototype.addOverlay;
const originalRemoveOverlay = CodeMirror.prototype.removeOverlay;
CodeMirror.prototype.addOverlay = addOverlay;
CodeMirror.prototype.removeOverlay = removeOverlay;
return;
function shouldIntercept(overlay) {
const hlState = this.state.matchHighlighter || {};
return overlay === hlState.overlay && (hlState.options || {}).showToken;
}
function addOverlay() {
return shouldIntercept.apply(this, arguments) &&
addOverlayForHighlighter.apply(this, arguments) ||
originalAddOverlay.apply(this, arguments);
}
function removeOverlay() {
return shouldIntercept.apply(this, arguments) &&
removeOverlayForHighlighter.apply(this, arguments) ||
originalRemoveOverlay.apply(this, arguments);
}
function addOverlayForHighlighter(overlay) {
const state = this.state.matchHighlighter || {};
const helper = state.stylusMHLHelper || {};
if (helper.matchesonscroll) {
// restore the original addon's unwanted removeOverlay effects
// (in case the token under cursor hasn't changed)
state.matchesonscroll = helper.matchesonscroll;
state.overlay = helper.overlay;
helper.matchesonscroll = null;
helper.overlay = null;
return true;
}
if (overlay.token !== tokenHook) {
overlay.stylusMHLHelper = {
token: overlay.token,
occurrences: 0,
};
overlay.token = tokenHook;
}
clearTimeout(helper.hookTimer);
}
function tokenHook(stream) {
const style = this.stylusMHLHelper.token.call(this, stream);
if (style !== 'matchhighlight') {
return style;
}
const num = ++this.stylusMHLHelper.occurrences;
if (num === 1) {
stream.lineOracle.doc.cm.display.wrapper.classList.remove(HL_APPROVED);
} else if (num === 2) {
stream.lineOracle.doc.cm.display.wrapper.classList.add(HL_APPROVED);
}
return style;
}
function removeOverlayForHighlighter() {
const state = this.state.matchHighlighter || {};
const {query} = state.matchesonscroll || {};
if (!query) {
return;
}
const {line, ch} = this.getCursor();
const rx = query instanceof RegExp ? query : new RegExp(`\\b${query}\\b`);
const start = Math.max(0, ch - rx.source.length + 4 + 1);
const end = ch + rx.source.length - 4;
const area = this.getLine(line).substring(start, end);
const startInArea = (area.match(rx) || {}).index;
if (start + startInArea <= ch) {
// same token on cursor => prevent the highlighter from rerunning
state.stylusMHLHelper = {
overlay: state.overlay,
matchesonscroll: state.matchesonscroll,
hookTimer: setTimeout(removeOverlayIfExpired, 0, this, state),
};
state.matchesonscroll = null;
return true;
}
}
function removeOverlayIfExpired(self, state) {
const {overlay, matchesonscroll} = state.stylusMHLHelper || {};
if (overlay) {
originalRemoveOverlay.call(self, overlay);
}
if (matchesonscroll) {
matchesonscroll.clear();
}
state.stylusMHLHelper = null;
}
})();