diff --git a/src/1p-filters.html b/src/1p-filters.html index b5b0d25d9..e99dd8a05 100644 --- a/src/1p-filters.html +++ b/src/1p-filters.html @@ -47,11 +47,11 @@ - + diff --git a/src/asset-viewer.html b/src/asset-viewer.html index 0aca82e51..c3a2badde 100644 --- a/src/asset-viewer.html +++ b/src/asset-viewer.html @@ -35,11 +35,11 @@ body { - + diff --git a/src/css/codemirror.css b/src/css/codemirror.css index 411804f7f..e17b5caa3 100644 --- a/src/css/codemirror.css +++ b/src/css/codemirror.css @@ -83,8 +83,8 @@ div.CodeMirror span.CodeMirror-matchingbracket { border: 1px solid gray; border-radius: 3px; display: inline-flex; - max-width: 50vw; - width: 16em; + min-width: 14em; + width: 30vw; } .cm-search-widget-input > input { border: 0; @@ -93,12 +93,10 @@ div.CodeMirror span.CodeMirror-matchingbracket { } .cm-search-widget-input > .cm-search-widget-count { align-items: center; - color: #888; display: none; flex-grow: 0; font-size: 80%; padding: 0 0.4em; - pointer-events: none; } .cm-search-widget[data-query] .cm-search-widget-count { display: inline-flex; diff --git a/src/js/codemirror/search-thread.js b/src/js/codemirror/search-thread.js new file mode 100644 index 000000000..29d4465d6 --- /dev/null +++ b/src/js/codemirror/search-thread.js @@ -0,0 +1,196 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2020-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { +// >>>>> start of local scope + +/******************************************************************************/ + +// Worker context + +if ( + self.WorkerGlobalScope instanceof Object && + self instanceof self.WorkerGlobalScope +) { + let content = ''; + + const doSearch = function(details) { + const reEOLs = /\n\r|\r\n|\n|\r/g; + const t1 = Date.now() + 750; + + let reSearch; + try { + reSearch = new RegExp(details.pattern, details.flags); + } catch(ex) { + return; + } + + const response = []; + const maxOffset = content.length; + let iLine = 0; + let iOffset = 0; + let size = 0; + while ( iOffset < maxOffset ) { + // Find next match + const match = reSearch.exec(content); + if ( match === null ) { break; } + // Find number of line breaks between last and current match. + reEOLs.lastIndex = 0; + const eols = content.slice(iOffset, match.index).match(reEOLs); + if ( Array.isArray(eols) ) { + iLine += eols.length; + } + // Store line + response.push(iLine); + size += 1; + // Find next line break. + reEOLs.lastIndex = reSearch.lastIndex; + const eol = reEOLs.exec(content); + iOffset = eol !== null + ? reEOLs.lastIndex + : content.length; + reSearch.lastIndex = iOffset; + iLine += 1; + // Quit if this takes too long + if ( (size & 0x3FF) === 0 && Date.now() >= t1 ) { break; } + } + + return response; + }; + + self.onmessage = function(e) { + const msg = e.data; + + switch ( msg.what ) { + case 'setHaystack': + content = msg.content; + break; + + case 'doSearch': + const response = doSearch(msg); + self.postMessage({ id: msg.id, response }); + break; + } + }; + + return; +} + +/******************************************************************************/ + +// Main context + +{ + const workerTTL = 5 * 60 * 1000; + const pendingResponses = new Map(); + + let worker; + let workerTTLTimer; + let messageId = 1; + + const onWorkerMessage = function(e) { + const msg = e.data; + const resolver = pendingResponses.get(msg.id); + if ( resolver === undefined ) { return; } + pendingResponses.delete(msg.id); + resolver(msg.response); + }; + + const cancelPendingTasks = function() { + for ( const resolver of pendingResponses.values() ) { + resolver(); + } + pendingResponses.clear(); + }; + + const destroy = function() { + shutdown(); + self.searchThread = undefined; + }; + + const shutdown = function() { + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + workerTTLTimer = undefined; + } + if ( worker === undefined ) { return; } + worker.terminate(); + worker.onmessage = undefined; + worker = undefined; + cancelPendingTasks(); + }; + + const init = function() { + if ( self.searchThread instanceof Object === false ) { return; } + if ( worker === undefined ) { + worker = new Worker('js/codemirror/search-thread.js'); + worker.onmessage = onWorkerMessage; + } + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + } + workerTTLTimer = vAPI.setTimeout(shutdown, workerTTL); + self.addEventListener('beforeunload', ( ) => { + destroy(); + }, { once: true }); + }; + + const setHaystack = function(content) { + init(); + worker.postMessage({ what: 'setHaystack', content }); + }; + + const search = function(query, overwrite = true) { + init(); + if ( worker instanceof Object === false ) { + return Promise.resolve(); + } + if ( overwrite ) { + cancelPendingTasks(); + } + const id = messageId++; + worker.postMessage({ + what: 'doSearch', + id, + pattern: query.source, + flags: query.flags, + isRE: query instanceof RegExp + }); + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); + }; + + self.searchThread = { setHaystack, search, shutdown }; +} + +/******************************************************************************/ + +// <<<<< end of local scope +})(); + +/******************************************************************************/ + +void 0; diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js index d3420f7bb..cc91b742d 100644 --- a/src/js/codemirror/search.js +++ b/src/js/codemirror/search.js @@ -3,8 +3,16 @@ // I added/removed and modified code in order to get a closer match to a // browser's built-in find-in-page feature which are just enough for // uBlock Origin. +// +// This file was originally wholly imported from: +// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js +// +// And has been modified over time to better suit uBO's usage and coding style: +// https://github.com/gorhill/uBlock/commits/master/src/js/codemirror/search.js +// +// The original copyright notice is reproduced below: - +// ===== // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: http://codemirror.net/LICENSE @@ -15,44 +23,39 @@ // Ctrl-G (or whatever is bound to findNext) press. You prevent a // replace by making sure the match is no longer selected when hitting // Ctrl-G. - -/* globals define, require, CodeMirror */ +// ===== 'use strict'; -(function(mod) { - if (typeof exports === "object" && typeof module === "object") // CommonJS - mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); - else if (typeof define === "function" && define.amd) // AMD - define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { +(function(CodeMirror) { - function searchOverlay(query, caseInsensitive) { - if (typeof query === "string") - query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); - else if (!query.global) - query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); + const searchOverlay = function(query, caseInsensitive) { + if ( typeof query === 'string' ) + query = new RegExp( + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), + caseInsensitive ? 'gi' : 'g' + ); + else if ( !query.global ) + query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g'); return { token: function(stream) { query.lastIndex = stream.pos; - var match = query.exec(stream.string); - if (match && match.index === stream.pos) { + const match = query.exec(stream.string); + if ( match && match.index === stream.pos ) { stream.pos += match[0].length || 1; - return "searching"; - } else if (match) { + return 'searching'; + } else if ( match ) { stream.pos = match.index; } else { stream.skipToEnd(); } } }; - } + }; - function searchWidgetKeydownHandler(cm, ev) { - var keyName = CodeMirror.keyName(ev); + const searchWidgetKeydownHandler = function(cm, ev) { + const keyName = CodeMirror.keyName(ev); if ( !keyName ) { return; } CodeMirror.lookupKey( keyName, @@ -64,9 +67,9 @@ } } ); - } + }; - function searchWidgetInputHandler(cm) { + const searchWidgetInputHandler = function(cm) { let state = getSearchState(cm); if ( queryTextFromSearchWidget(cm) === state.queryText ) { return; } if ( state.queryTimer !== null ) { @@ -79,10 +82,10 @@ }, 350 ); - } + }; - function searchWidgetClickHandler(cm, ev) { - var tcl = ev.target.classList; + const searchWidgetClickHandler = function(cm, ev) { + const tcl = ev.target.classList; if ( tcl.contains('cm-search-widget-up') ) { findNext(cm, -1); } else if ( tcl.contains('cm-search-widget-down') ) { @@ -93,27 +96,25 @@ } else { ev.stopImmediatePropagation(); } - } + }; - function queryTextFromSearchWidget(cm) { + const queryTextFromSearchWidget = function(cm) { return getSearchState(cm).widget.querySelector('input[type="search"]').value; - } + }; - function queryTextToSearchWidget(cm, q) { - var input = getSearchState(cm).widget.querySelector('input[type="search"]'); + const queryTextToSearchWidget = function(cm, q) { + const input = getSearchState(cm).widget.querySelector('input[type="search"]'); if ( typeof q === 'string' && q !== input.value ) { input.value = q; } input.setSelectionRange(0, input.value.length); input.focus(); - } + }; - function SearchState(cm) { + const SearchState = function(cm) { this.query = null; - this.overlay = null; this.panel = null; - const widgetParent = - document.querySelector('.cm-search-widget-template').cloneNode(true); + const widgetParent = document.querySelector('.cm-search-widget-template').cloneNode(true); this.widget = widgetParent.children[0]; this.widget.addEventListener('keydown', searchWidgetKeydownHandler.bind(null, cm)); this.widget.addEventListener('input', searchWidgetInputHandler.bind(null, cm)); @@ -123,16 +124,29 @@ } this.queryText = ''; this.queryTimer = null; - } + this.dirty = true; + this.lines = []; + cm.on('changes', (cm, changes) => { + for ( const change of changes ) { + if ( change.text.length !== 0 || change.removed !== 0 ) { + this.dirty = true; + break; + } + } + }); + cm.on('cursorActivity', cm => { + updateRank(cm); + }); + }; // We want the search widget to behave as if the focus was on the // CodeMirror editor. const reSearchCommands = /^(?:find|findNext|findPrev|newlineAndIndent)$/; - function widgetCommandHandler(cm, command) { + const widgetCommandHandler = function(cm, command) { if ( reSearchCommands.test(command) === false ) { return false; } - var queryText = queryTextFromSearchWidget(cm); + const queryText = queryTextFromSearchWidget(cm); if ( command === 'find' ) { queryTextToSearchWidget(cm); return true; @@ -141,101 +155,202 @@ findNext(cm, command === 'findPrev' ? -1 : 1); } return true; - } + }; - function getSearchState(cm) { + const getSearchState = function(cm) { return cm.state.search || (cm.state.search = new SearchState(cm)); - } + }; - function queryCaseInsensitive(query) { - return typeof query === "string" && query === query.toLowerCase(); - } + const queryCaseInsensitive = function(query) { + return typeof query === 'string' && query === query.toLowerCase(); + }; - function getSearchCursor(cm, query, pos) { - // Heuristic: if the query string is all lowercase, do a case insensitive search. + // Heuristic: if the query string is all lowercase, do a case insensitive search. + const getSearchCursor = function(cm, query, pos) { return cm.getSearchCursor( query, pos, { caseFold: queryCaseInsensitive(query), multiline: false } ); - } + }; // https://github.com/uBlockOrigin/uBlock-issues/issues/658 // Modified to backslash-escape ONLY widely-used control characters. - function parseString(string) { - return string.replace(/\\[nrt\\]/g, function(match) { - if (match === "\\n") return "\n"; - if (match === "\\r") return "\r"; - if (match === '\\t') return '\t'; - if (match === '\\\\') return '\\'; + const parseString = function(string) { + return string.replace(/\\[nrt\\]/g, match => { + if ( match === '\\n' ) { return '\n'; } + if ( match === '\\r' ) { return '\r'; } + if ( match === '\\t' ) { return '\t'; } + if ( match === '\\\\' ) { return '\\'; } return match; }); - } + }; - // FIX: use all potential regex flags as is, and if this throws, treat - // the query string as plain text. - function parseQuery(query) { - let isRE = query.match(/^\/(.*)\/([a-z]*)$/); - if ( isRE ) { + const reEscape = /[.*+\-?^${}()|[\]\\]/g; + + // Must always return a RegExp object. + // + // Assume case-sensitivity if there is at least one uppercase in plain + // query text. + const parseQuery = function(query) { + let flags = 'i'; + let reParsed = query.match(/^\/(.+)\/([iu]*)$/); + if ( reParsed !== null ) { try { - query = new RegExp(isRE[1], isRE[2]); + const re = new RegExp(reParsed[1], reParsed[2]); + query = re.source; + flags = re.flags; } catch (e) { - isRE = false; + reParsed = null; } } - if ( isRE === false ) { - query = parseString(query); + if ( reParsed === null ) { + if ( /[A-Z]/.test(query) ) { flags = ''; } + query = parseString(query).replace(reEscape, '\\$&'); } if ( typeof query === 'string' ? query === '' : query.test('') ) { - query = /x^/; + query = 'x^'; } - return query; - } + return new RegExp(query, 'gm' + flags); + }; - function startSearch(cm, state) { + let intlNumberFormat; + + const formatNumber = function(n) { + if ( intlNumberFormat === undefined ) { + intlNumberFormat = null; + if ( Intl.NumberFormat instanceof Function ) { + const intl = new Intl.NumberFormat(undefined, { + notation: 'compact', + maximumSignificantDigits: 3 + }); + if ( + intl.resolvedOptions instanceof Function && + intl.resolvedOptions().hasOwnProperty('notation') + ) { + intlNumberFormat = intl; + } + } + } + return n > 10000 && intlNumberFormat instanceof Object + ? intlNumberFormat.format(n) + : n.toLocaleString(); + }; + + const updateCount = function(cm) { + const state = getSearchState(cm); + const count = state.lines.length; + const span = state.widget.querySelector( + '.cm-search-widget-count > span:nth-of-type(2)' + ); + span.textContent = formatNumber(count); + span.title = count.toLocaleString(); + }; + + const updateRank = function(cm) { + const state = getSearchState(cm); + const lines = state.lines; + const current = cm.getCursor().line; + let l = 0; + let r = lines.length; + let i = -1; + while ( l < r ) { + i = l + r >>> 1; + const candidate = lines[i]; + if ( current === candidate ) { break; } + if ( current < candidate ) { + r = i; + } else /* if ( current > candidate ) */ { + l = i + 1; + } + } + let text = ''; + if ( i !== -1 ) { + text = formatNumber(i + 1); + if ( lines[i] !== current ) { + text = '~' + text; + } + text = text + '\xA0/\xA0'; + } + const span = state.widget.querySelector( + '.cm-search-widget-count > span:nth-of-type(1)' + ); + span.textContent = text; + }; + + const startSearch = function(cm, state) { state.query = parseQuery(state.queryText); - if ( state.overlay ) { + if ( state.overlay !== undefined ) { cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); } state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); cm.addOverlay(state.overlay); - if ( cm.showMatchesOnScrollbar ) { - if ( state.annotate ) { - state.annotate.clear(); - state.annotate = null; - } - state.annotate = cm.showMatchesOnScrollbar( - state.query, - queryCaseInsensitive(state.query), - { multiline: false } - ); - let count = state.annotate.matches.length; - state.widget - .querySelector('.cm-search-widget-count > span:nth-of-type(2)') - .textContent = count > 1000 ? '1000+' : count; - state.widget.setAttribute('data-query', state.queryText); - // Ensure the caret is visible - let input = state.widget.querySelector('.cm-search-widget-input > input'); - input.selectionStart = input.selectionStart; + if ( state.dirty ) { + self.searchThread.setHaystack(cm.getValue()); + state.dirty = false; } - } + self.searchThread.search(state.query).then(lines => { + if ( Array.isArray(lines) === false ) { return; } + state.lines = lines; + const count = lines.length; + updateRank(cm); + updateCount(cm); + if ( state.annotate !== undefined ) { + state.annotate.clear(); + state.annotate = undefined; + } + if ( count === 0 ) { return; } + state.annotate = cm.annotateScrollbar('CodeMirror-search-match'); + const annotations = []; + let lineBeg = -1; + let lineEnd = -1; + for ( const line of lines ) { + if ( lineBeg === -1 ) { + lineBeg = line; + lineEnd = line + 1; + continue; + } else if ( line === lineEnd ) { + lineEnd = line + 1; + continue; + } + annotations.push({ + from: { line: lineBeg, ch: 0 }, + to: { line: lineEnd, ch: 0 } + }); + lineBeg = -1; + } + if ( lineBeg !== -1 ) { + annotations.push({ + from: { line: lineBeg, ch: 0 }, + to: { line: lineEnd, ch: 0 } + }); + } + state.annotate.update(annotations); + }); + state.widget.setAttribute('data-query', state.queryText); + // Ensure the caret is visible + let input = state.widget.querySelector('.cm-search-widget-input > input'); + input.selectionStart = input.selectionStart; + }; - function findNext(cm, dir, callback) { + const findNext = function(cm, dir, callback) { cm.operation(function() { - var state = getSearchState(cm); + const state = getSearchState(cm); if ( !state.query ) { return; } - var cursor = getSearchCursor( + let cursor = getSearchCursor( cm, state.query, dir <= 0 ? cm.getCursor('from') : cm.getCursor('to') ); - let previous = dir < 0; + const previous = dir < 0; if (!cursor.find(previous)) { cursor = getSearchCursor( cm, state.query, - previous ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0) + previous + ? CodeMirror.Pos(cm.lastLine()) + : CodeMirror.Pos(cm.firstLine(), 0) ); if (!cursor.find(previous)) return; } @@ -243,21 +358,22 @@ cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20); if (callback) callback(cursor.from(), cursor.to()); }); - } + }; - function clearSearch(cm, hard) { + const clearSearch = function(cm, hard) { cm.operation(function() { - var state = getSearchState(cm); + const state = getSearchState(cm); if ( state.query ) { state.query = state.queryText = null; } - if ( state.overlay ) { + state.lines = []; + if ( state.overlay !== undefined ) { cm.removeOverlay(state.overlay); - state.overlay = null; + state.overlay = undefined; } if ( state.annotate ) { state.annotate.clear(); - state.annotate = null; + state.annotate = undefined; } state.widget.removeAttribute('data-query'); if ( hard ) { @@ -267,15 +383,15 @@ cm.state.search = null; } }); - } + }; - function findCommit(cm, dir) { - var state = getSearchState(cm); + const findCommit = function(cm, dir) { + const state = getSearchState(cm); if ( state.queryTimer !== null ) { clearTimeout(state.queryTimer); state.queryTimer = null; } - var queryText = queryTextFromSearchWidget(cm); + const queryText = queryTextFromSearchWidget(cm); if ( queryText === state.queryText ) { return; } state.queryText = queryText; if ( state.queryText === '' ) { @@ -286,12 +402,12 @@ findNext(cm, dir); }); } - } + }; - function findCommand(cm) { - var queryText = cm.getSelection() || undefined; + const findCommand = function(cm) { + let queryText = cm.getSelection() || undefined; if ( !queryText ) { - var word = cm.findWordAt(cm.getCursor()); + const word = cm.findWordAt(cm.getCursor()); queryText = cm.getRange(word.anchor, word.head); if ( /^\W|\W$/.test(queryText) ) { queryText = undefined; @@ -300,17 +416,17 @@ } queryTextToSearchWidget(cm, queryText); findCommit(cm, 1); - } + }; - function findNextCommand(cm) { - var state = getSearchState(cm); + const findNextCommand = function(cm) { + const state = getSearchState(cm); if ( state.query ) { return findNext(cm, 1); } - } + }; - function findPrevCommand(cm) { - var state = getSearchState(cm); + const findPrevCommand = function(cm) { + const state = getSearchState(cm); if ( state.query ) { return findNext(cm, -1); } - } + }; { const searchWidgetTemplate = @@ -318,13 +434,13 @@ '
' + ''; @@ -341,4 +457,4 @@ CodeMirror.defineInitHook(function(cm) { getSearchState(cm); }); -}); +})(self.CodeMirror); diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index 7d9af5a9a..7f39244fe 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -457,7 +457,6 @@ if ( return new Promise(resolve => { pendingResponses.set(id, resolve); }); - }; // This tells the worker that filter lists may have changed. diff --git a/src/lib/codemirror/addon/search/matchesonscrollbar.js b/src/lib/codemirror/addon/search/matchesonscrollbar.js deleted file mode 100644 index 8a4a82758..000000000 --- a/src/lib/codemirror/addon/search/matchesonscrollbar.js +++ /dev/null @@ -1,97 +0,0 @@ -// 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"), require("./searchcursor"), require("../scroll/annotatescrollbar")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) { - if (typeof options == "string") options = {className: options}; - if (!options) options = {}; - return new SearchAnnotation(this, query, caseFold, options); - }); - - function SearchAnnotation(cm, query, caseFold, options) { - this.cm = cm; - this.options = options; - var annotateOptions = {listenForChanges: false}; - for (var prop in options) annotateOptions[prop] = options[prop]; - if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match"; - this.annotation = cm.annotateScrollbar(annotateOptions); - this.query = query; - this.caseFold = caseFold; - this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1}; - this.matches = []; - this.update = null; - - this.findMatches(); - this.annotation.update(this.matches); - - var self = this; - cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); }); - } - - var MAX_MATCHES = 1000; - - SearchAnnotation.prototype.findMatches = function() { - if (!this.gap) return; - for (var i = 0; i < this.matches.length; i++) { - var match = this.matches[i]; - if (match.from.line >= this.gap.to) break; - if (match.to.line >= this.gap.from) this.matches.splice(i--, 1); - } - var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline}); - var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES; - while (cursor.findNext()) { - var match = {from: cursor.from(), to: cursor.to()}; - if (match.from.line >= this.gap.to) break; - this.matches.splice(i++, 0, match); - if (this.matches.length > maxMatches) break; - } - this.gap = null; - }; - - function offsetLine(line, changeStart, sizeChange) { - if (line <= changeStart) return line; - return Math.max(changeStart, line + sizeChange); - } - - SearchAnnotation.prototype.onChange = function(change) { - var startLine = change.from.line; - var endLine = CodeMirror.changeEnd(change).line; - var sizeChange = endLine - change.to.line; - if (this.gap) { - this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line); - this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line); - } else { - this.gap = {from: change.from.line, to: endLine + 1}; - } - - if (sizeChange) for (var i = 0; i < this.matches.length; i++) { - var match = this.matches[i]; - var newFrom = offsetLine(match.from.line, startLine, sizeChange); - if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch); - var newTo = offsetLine(match.to.line, startLine, sizeChange); - if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch); - } - clearTimeout(this.update); - var self = this; - this.update = setTimeout(function() { self.updateAfterChange(); }, 250); - }; - - SearchAnnotation.prototype.updateAfterChange = function() { - this.findMatches(); - this.annotation.update(this.matches); - }; - - SearchAnnotation.prototype.clear = function() { - this.cm.off("change", this.changeHandler); - this.annotation.clear(); - }; -}); diff --git a/src/whitelist.html b/src/whitelist.html index 2a1dbe022..b06744c74 100644 --- a/src/whitelist.html +++ b/src/whitelist.html @@ -41,12 +41,12 @@ - +