diff --git a/src/1p-filters.html b/src/1p-filters.html
index 27a2c81d9..ba032e810 100644
--- a/src/1p-filters.html
+++ b/src/1p-filters.html
@@ -39,11 +39,11 @@
-
+
diff --git a/src/asset-viewer.html b/src/asset-viewer.html
index 6975d722b..83c64add8 100644
--- a/src/asset-viewer.html
+++ b/src/asset-viewer.html
@@ -30,11 +30,11 @@
-
+
diff --git a/src/css/codemirror.css b/src/css/codemirror.css
index 5d3eafdc7..7b425c68a 100644
--- a/src/css/codemirror.css
+++ b/src/css/codemirror.css
@@ -36,14 +36,17 @@
direction: ltr;
display: flex;
flex-shrink: 0;
- font-size: 110%;
- justify-content: center;
+ justify-content: space-between;
padding: 0.5em;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
z-index: 1000;
}
+.cm-search-widget-input {
+ display: inline-flex;
+ flex-grow: 1;
+ }
.cm-search-widget .fa-icon {
fill: #888;
font-size: 140%;
@@ -51,42 +54,32 @@
.cm-search-widget .fa-icon:not(.fa-icon-ro):hover {
fill: #000;
}
-.cm-search-widget-input {
+.cm-search-widget-input input {
border: 1px solid gray;
- border-radius: 3px;
display: inline-flex;
- max-width: 50vw;
- width: 16em;
- }
-.cm-search-widget-input > input {
- border: 0;
flex-grow: 1;
- width: 100%;
+ max-width: 16em;
}
-.cm-search-widget-input > .cm-search-widget-count {
+.cm-search-widget-count {
align-items: center;
- color: #888;
- display: none;
+ display: inline-flex;
flex-grow: 0;
- font-size: 80%;
- padding: 0 0.4em;
- pointer-events: none;
+ font-size: 95%;
+ min-width: 6em;
+ visibility: hidden;
}
-.cm-search-widget[data-query] .cm-search-widget-count {
- display: inline-flex;
+.cm-search-widget[data-query] .cm-search-widget-count:not(:empty) {
+ visibility: visible;
}
.cm-search-widget .cm-search-widget-button:hover {
color: #000;
}
-.cm-search-widget .sourceURL {
- padding-left: 0.5em;
- padding-right: 0.5em;
- position: absolute;
- left: 0;
- }
.cm-search-widget .sourceURL[href=""] {
display: none;
}
+.cm-searching {
+ border: 1px dotted black;
+ }
.CodeMirror-merge-l-deleted {
background-image: none;
diff --git a/src/dyna-rules.html b/src/dyna-rules.html
index 0913467cb..718efe535 100644
--- a/src/dyna-rules.html
+++ b/src/dyna-rules.html
@@ -34,7 +34,7 @@
-
+
diff --git a/src/js/codemirror/search-thread.js b/src/js/codemirror/search-thread.js
new file mode 100644
index 000000000..6c925ac80
--- /dev/null
+++ b/src/js/codemirror/search-thread.js
@@ -0,0 +1,203 @@
+/*******************************************************************************
+
+ 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 ( let 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);
+ };
+
+ const needHaystack = function() {
+ return worker instanceof Object === false;
+ };
+
+ 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.addEventListener(
+ 'beforeunload',
+ ( ) => { destroy(); },
+ { once: true }
+ );
+
+ self.searchThread = { needHaystack, setHaystack, search, shutdown };
+}
+
+/******************************************************************************/
+
+// <<<<< end of local scope
+})();
+
+/******************************************************************************/
+
+void 0;
diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js
index 1a2771428..b1be39474 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 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");
+(function(CodeMirror) {
+
+ 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,60 +67,54 @@
}
}
);
- }
-
- function searchWidgetTimerHandler(cm) {
- var state = getSearchState(cm);
- state.queryTimer = null;
- findCommit(cm);
- }
+ };
- function searchWidgetInputHandler(cm) {
- var state = getSearchState(cm);
- if ( queryTextFromSearchWidget(cm) !== state.queryText ) {
- if ( state.queryTimer !== null ) {
- clearTimeout(state.queryTimer);
- }
- state.queryTimer = setTimeout(
- searchWidgetTimerHandler.bind(null, cm),
- 350
- );
+ const searchWidgetInputHandler = function(cm) {
+ let state = getSearchState(cm);
+ if ( queryTextFromSearchWidget(cm) === state.queryText ) { return; }
+ if ( state.queryTimer !== null ) {
+ clearTimeout(state.queryTimer);
}
- }
+ state.queryTimer = setTimeout(
+ () => {
+ state.queryTimer = null;
+ findCommit(cm, 0);
+ },
+ 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, true);
+ findNext(cm, -1);
} else if ( tcl.contains('cm-search-widget-down') ) {
- findNext(cm, false);
+ findNext(cm, 1);
}
if ( ev.target.localName !== 'input' ) {
ev.preventDefault();
} else {
ev.stopImmediatePropagation();
}
- }
+ };
- function queryTextFromSearchWidget(cm) {
- return getSearchState(cm).widget.querySelector('input[type="text"]').value;
- }
+ 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="text"]');
+ 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));
@@ -127,127 +124,250 @@
}
this.queryText = '';
this.queryTimer = null;
- }
+ this.dirty = true;
+ this.lines = [];
+ cm.on('changes', (cm, changes) => {
+ for ( let change of changes ) {
+ if ( change.text.length !== 0 || change.removed !== 0 ) {
+ this.dirty = true;
+ break;
+ }
+ }
+ });
+ cm.on('cursorActivity', cm => {
+ updateCount(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;
}
if ( queryText.length !== 0 ) {
- findNext(cm, command === 'findPrev');
+ 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: true}
+ { caseFold: queryCaseInsensitive(query), multiline: false }
);
- }
-
- function parseString(string) {
- return string.replace(/\\(.)/g, function(_, ch) {
- if (ch === "n") return "\n";
- if (ch === "r") return "\r";
- return ch;
+ };
+
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/658
+ // Modified to backslash-escape ONLY widely-used control characters.
+ 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;
});
- }
-
- function parseQuery(query) {
- var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
- if (isRE) {
- try { query = new RegExp(isRE[1], isRE[2].indexOf("i") === -1 ? "" : "i"); }
- catch(e) {} // Not a regular expression after all, do a string search
- } else {
- query = parseString(query);
+ };
+
+ 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 {
+ const re = new RegExp(reParsed[1], reParsed[2]);
+ query = re.source;
+ flags = re.flags;
+ }
+ catch (e) {
+ reParsed = null;
+ }
}
- if (typeof query === "string" ? query === "" : query.test(""))
- query = /x^/;
- return query;
- }
-
- function startSearch(cm, state) {
+ if ( reParsed === null ) {
+ if ( /[A-Z]/.test(query) ) { flags = ''; }
+ query = parseString(query).replace(reEscape, '\\$&');
+ }
+ if ( typeof query === 'string' ? query === '' : query.test('') ) {
+ query = 'x^';
+ }
+ return new RegExp(query, 'gm' + flags);
+ };
+
+ 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 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 count = lines.length;
+ text += formatNumber(count);
+ const span = state.widget.querySelector('.cm-search-widget-count');
+ span.textContent = text;
+ span.title = count.toLocaleString();
+ };
+
+ 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 ) {
+ if ( state.dirty || self.searchThread.needHaystack() ) {
+ 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;
+ updateCount(cm);
+ if ( state.annotate !== undefined ) {
state.annotate.clear();
- state.annotate = null;
+ state.annotate = undefined;
}
- state.annotate = cm.showMatchesOnScrollbar(
- state.query,
- queryCaseInsensitive(state.query)
- );
- 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 ( count === 0 ) { return; }
+ state.annotate = cm.annotateScrollbar('CodeMirror-search-match');
+ const annotations = [];
+ let lineBeg = -1;
+ let lineEnd = -1;
+ for ( let 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
+ const input = state.widget.querySelector('.cm-search-widget-input input');
+ input.selectionStart = input.selectionStart;
+ };
- function findNext(cm, rev, 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,
- rev ? cm.getCursor('from') : cm.getCursor('to')
+ dir <= 0 ? cm.getCursor('from') : cm.getCursor('to')
);
- if (!cursor.find(rev)) {
+ const previous = dir < 0;
+ if (!cursor.find(previous)) {
cursor = getSearchCursor(
cm,
state.query,
- rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)
+ previous
+ ? CodeMirror.Pos(cm.lastLine())
+ : CodeMirror.Pos(cm.firstLine(), 0)
);
- if (!cursor.find(rev)) return;
+ if (!cursor.find(previous)) return;
}
cm.setSelection(cursor.from(), cursor.to());
- cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
+ const { clientHeight } = cm.getScrollInfo();
+ cm.scrollIntoView(
+ { from: cursor.from(), to: cursor.to() },
+ clientHeight >>> 1
+ );
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 ) {
@@ -257,15 +377,15 @@
cm.state.search = null;
}
});
- }
+ };
- function findCommit(cm) {
- 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 === '' ) {
@@ -273,15 +393,15 @@
} else {
cm.operation(function() {
startSearch(cm, state);
- findNext(cm, false);
+ 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;
@@ -289,32 +409,30 @@
cm.setCursor(word.anchor);
}
queryTextToSearchWidget(cm, queryText);
- findCommit(cm);
- }
+ findCommit(cm, 1);
+ };
- function findNextCommand(cm) {
- var state = getSearchState(cm);
- if ( state.query ) { return findNext(cm, false); }
- }
+ const findNextCommand = function(cm) {
+ const state = getSearchState(cm);
+ if ( state.query ) { return findNext(cm, 1); }
+ };
- function findPrevCommand(cm) {
- var state = getSearchState(cm);
- if ( state.query ) { return findNext(cm, true); }
- }
+ const findPrevCommand = function(cm) {
+ const state = getSearchState(cm);
+ if ( state.query ) { return findNext(cm, -1); }
+ };
{
const searchWidgetTemplate =
'';
@@ -331,4 +449,4 @@
CodeMirror.defineInitHook(function(cm) {
getSearchState(cm);
});
-});
+})(self.CodeMirror);
diff --git a/src/lib/codemirror/addon/search/matchesonscrollbar.js b/src/lib/codemirror/addon/search/matchesonscrollbar.js
deleted file mode 100644
index 8d1922897..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: http://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), this.caseFold);
- 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/logger-ui.html b/src/logger-ui.html
index ef7e3939a..e94883b5f 100644
--- a/src/logger-ui.html
+++ b/src/logger-ui.html
@@ -45,7 +45,7 @@
-
+
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 @@
-
+