diff --git a/edit.html b/edit.html index 86cd5e57..2f20f8ee 100644 --- a/edit.html +++ b/edit.html @@ -37,7 +37,7 @@ - + @@ -61,6 +61,7 @@ + diff --git a/edit/match-highlighter-helper.js b/edit/match-highlighter-helper.js new file mode 100644 index 00000000..6036d8ce --- /dev/null +++ b/edit/match-highlighter-helper.js @@ -0,0 +1,99 @@ +/* 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; + } +})(); diff --git a/install-usercss.html b/install-usercss.html index 1b40c75e..208d3c4d 100644 --- a/install-usercss.html +++ b/install-usercss.html @@ -23,6 +23,7 @@ + @@ -32,7 +33,7 @@ - + diff --git a/vendor-overwrites/codemirror/addon/search/match-highlighter.js b/vendor/codemirror/addon/search/match-highlighter.js similarity index 81% rename from vendor-overwrites/codemirror/addon/search/match-highlighter.js rename to vendor/codemirror/addon/search/match-highlighter.js index f1b49d92..73ba0e05 100644 --- a/vendor-overwrites/codemirror/addon/search/match-highlighter.js +++ b/vendor/codemirror/addon/search/match-highlighter.js @@ -19,12 +19,6 @@ // highlighting the matches. If annotateScrollbar is enabled, the occurences // will be highlighted on the scrollbar via the matchesonscrollbar addon. -/* STYLUS: hack start (part 1) */ -/* eslint curly: 1, brace-style:1, strict: 0, quotes: 0, semi: 1, indent: 1 */ -/* eslint no-var: 0, block-scoped-var: 0, no-redeclare: 0, no-unused-expressions: 1 */ -/* global CodeMirror, require, define */ -/* STYLUS: hack end (part 1) */ - (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("./matchesonscrollbar")); @@ -94,9 +88,7 @@ function addOverlay(cm, query, hasBoundary, style) { var state = cm.state.matchHighlighter; - /* STYLUS: hack start (part 2) */ - cm.addOverlay(state.overlay = makeOverlay(cm, query, hasBoundary, style)); - /* STYLUS: hack end (part 2) */ + cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style)); if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) { var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query; state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false, @@ -119,24 +111,16 @@ function highlightMatches(cm) { cm.operation(function() { var state = cm.state.matchHighlighter; + removeOverlay(cm); if (!cm.somethingSelected() && state.options.showToken) { var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken; var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; while (start && re.test(line.charAt(start - 1))) --start; while (end < line.length && re.test(line.charAt(end))) ++end; - /* STYLUS: hack start */ - const token = line.slice(start, end); - if (token !== state.lastToken) { - state.lastToken = token; - removeOverlay(cm); - if (token) { - addOverlay(cm, token, re, state.options.style); - } - } + if (start < end) + addOverlay(cm, line.slice(start, end), re, state.options.style); return; } - removeOverlay(cm); - /* STYLUS: hack end */ var from = cm.getCursor("from"), to = cm.getCursor("to"); if (from.line != to.line) return; if (state.options.wordsOnly && !isWord(cm, from, to)) return; @@ -169,28 +153,11 @@ (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); } - function makeOverlay(cm, query, hasBoundary, style) { - /* STYLUS: hack start (part 3) */ - const approvedClassName = `cm-${style}-approved`; - let timer; - let occurrences = 0; + function makeOverlay(query, hasBoundary, style) { return {token: function(stream) { - clearTimeout(timer); - timer = setTimeout(() => { - occurrences = 0; - timer = null; - }); if (stream.match(query) && - (!hasBoundary || boundariesAround(stream, hasBoundary))) { - occurrences++; - if (occurrences == 1) { - cm.display.wrapper.classList.remove(approvedClassName); - } else if (occurrences == 2) { - cm.display.wrapper.classList.add(approvedClassName); - } + (!hasBoundary || boundariesAround(stream, hasBoundary))) return style; - } - /* STYLUS: hack end (part 3) */ stream.next(); stream.skipTo(query.charAt(0)) || stream.skipToEnd(); }};