mirror of
https://git.axenov.dev/mirrors/cursor-free-vip.git
synced 2026-01-04 01:31:36 +03:00
Big Change Update
This commit is contained in:
199
uBlock0.chromium/js/codemirror/search-thread.js
Normal file
199
uBlock0.chromium/js/codemirror/search-thread.js
Normal file
@@ -0,0 +1,199 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
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 = { min: 5 };
|
||||
const pendingResponses = new Map();
|
||||
const workerTTLTimer = vAPI.defer.create(( ) => {
|
||||
shutdown();
|
||||
});
|
||||
|
||||
let worker;
|
||||
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 ( worker === undefined ) { return; }
|
||||
workerTTLTimer.off();
|
||||
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;
|
||||
}
|
||||
workerTTLTimer.offon(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;
|
||||
516
uBlock0.chromium/js/codemirror/search.js
Normal file
516
uBlock0.chromium/js/codemirror/search.js
Normal file
@@ -0,0 +1,516 @@
|
||||
// The following code is heavily based on the standard CodeMirror
|
||||
// search addon found at: https://codemirror.net/addon/search/search.js
|
||||
// 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
|
||||
|
||||
// Define search commands. Depends on dialog.js or another
|
||||
// implementation of the openDialog method.
|
||||
|
||||
// Replace works a little oddly -- it will do the replace on the next
|
||||
// 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.
|
||||
// =====
|
||||
|
||||
import { dom, qs$ } from '../dom.js';
|
||||
import { i18n$ } from '../i18n.js';
|
||||
|
||||
{
|
||||
const CodeMirror = self.CodeMirror;
|
||||
|
||||
CodeMirror.defineOption('maximizable', true, (cm, maximizable) => {
|
||||
if ( typeof maximizable !== 'boolean' ) { return; }
|
||||
const wrapper = cm.getWrapperElement();
|
||||
if ( wrapper === null ) { return; }
|
||||
const container = wrapper.closest('.codeMirrorContainer');
|
||||
if ( container === null ) { return; }
|
||||
container.dataset.maximizable = `${maximizable}`;
|
||||
});
|
||||
|
||||
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;
|
||||
const match = query.exec(stream.string);
|
||||
if ( match && match.index === stream.pos ) {
|
||||
stream.pos += match[0].length || 1;
|
||||
return 'searching';
|
||||
} else if ( match ) {
|
||||
stream.pos = match.index;
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const searchWidgetKeydownHandler = function(cm, ev) {
|
||||
const keyName = CodeMirror.keyName(ev);
|
||||
if ( !keyName ) { return; }
|
||||
CodeMirror.lookupKey(
|
||||
keyName,
|
||||
cm.getOption('keyMap'),
|
||||
function(command) {
|
||||
if ( widgetCommandHandler(cm, command) ) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const searchWidgetInputHandler = function(cm, ev) {
|
||||
const state = getSearchState(cm);
|
||||
if ( ev.isTrusted !== true ) {
|
||||
if ( state.queryText === '' ) {
|
||||
clearSearch(cm);
|
||||
} else {
|
||||
cm.operation(function() {
|
||||
startSearch(cm, state);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( queryTextFromSearchWidget(cm) === state.queryText ) { return; }
|
||||
state.queryTimer.offon(350);
|
||||
};
|
||||
|
||||
const searchWidgetClickHandler = (ev, cm) => {
|
||||
if ( ev.button !== 0 ) { return; }
|
||||
const target = ev.target;
|
||||
const tcl = target.classList;
|
||||
if ( tcl.contains('cm-search-widget-up') ) {
|
||||
findNext(cm, -1);
|
||||
} else if ( tcl.contains('cm-search-widget-down') ) {
|
||||
findNext(cm, 1);
|
||||
} else if ( tcl.contains('cm-linter-widget-up') ) {
|
||||
findNextError(cm, -1);
|
||||
} else if ( tcl.contains('cm-linter-widget-down') ) {
|
||||
findNextError(cm, 1);
|
||||
} else if ( tcl.contains('cm-maximize') ) {
|
||||
const container = target.closest('.codeMirrorContainer');
|
||||
if ( container !== null ) {
|
||||
container.classList.toggle('cm-maximized');
|
||||
}
|
||||
}
|
||||
if ( target.localName !== 'input' ) {
|
||||
cm.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const queryTextFromSearchWidget = function(cm) {
|
||||
return getSearchState(cm).widget.querySelector('input[type="search"]').value;
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
const SearchState = function(cm) {
|
||||
this.query = null;
|
||||
this.panel = null;
|
||||
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));
|
||||
this.widget.addEventListener('click', ev => {
|
||||
searchWidgetClickHandler(ev, cm);
|
||||
});
|
||||
if ( typeof cm.addPanel === 'function' ) {
|
||||
this.panel = cm.addPanel(this.widget);
|
||||
}
|
||||
this.queryText = '';
|
||||
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 => {
|
||||
updateCount(cm);
|
||||
});
|
||||
this.queryTimer = vAPI.defer.create(( ) => {
|
||||
findCommit(cm, 0);
|
||||
});
|
||||
};
|
||||
|
||||
// We want the search widget to behave as if the focus was on the
|
||||
// CodeMirror editor.
|
||||
|
||||
const reSearchCommands = /^(?:find|findNext|findPrev|newlineAndIndent)$/;
|
||||
|
||||
const widgetCommandHandler = function(cm, command) {
|
||||
if ( reSearchCommands.test(command) === false ) { return false; }
|
||||
const queryText = queryTextFromSearchWidget(cm);
|
||||
if ( command === 'find' ) {
|
||||
queryTextToSearchWidget(cm);
|
||||
return true;
|
||||
}
|
||||
if ( queryText.length !== 0 ) {
|
||||
findNext(cm, command === 'findPrev' ? -1 : 1);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const getSearchState = function(cm) {
|
||||
return cm.state.search || (cm.state.search = new SearchState(cm));
|
||||
};
|
||||
|
||||
const queryCaseInsensitive = function(query) {
|
||||
return typeof query === 'string' && query === query.toLowerCase();
|
||||
};
|
||||
|
||||
// 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.
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
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 ( 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().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 !== undefined ) {
|
||||
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
|
||||
}
|
||||
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
|
||||
cm.addOverlay(state.overlay);
|
||||
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 = 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);
|
||||
};
|
||||
|
||||
const findNext = function(cm, dir, callback) {
|
||||
cm.operation(function() {
|
||||
const state = getSearchState(cm);
|
||||
if ( !state.query ) { return; }
|
||||
let cursor = getSearchCursor(
|
||||
cm,
|
||||
state.query,
|
||||
dir <= 0 ? cm.getCursor('from') : cm.getCursor('to')
|
||||
);
|
||||
const previous = dir < 0;
|
||||
if (!cursor.find(previous)) {
|
||||
cursor = getSearchCursor(
|
||||
cm,
|
||||
state.query,
|
||||
previous
|
||||
? CodeMirror.Pos(cm.lastLine())
|
||||
: CodeMirror.Pos(cm.firstLine(), 0)
|
||||
);
|
||||
if (!cursor.find(previous)) return;
|
||||
}
|
||||
cm.setSelection(cursor.from(), cursor.to());
|
||||
const { clientHeight } = cm.getScrollInfo();
|
||||
cm.scrollIntoView(
|
||||
{ from: cursor.from(), to: cursor.to() },
|
||||
clientHeight >>> 1
|
||||
);
|
||||
if (callback) callback(cursor.from(), cursor.to());
|
||||
});
|
||||
};
|
||||
|
||||
const findNextError = function(cm, dir) {
|
||||
const doc = cm.getDoc();
|
||||
const cursor = cm.getCursor('from');
|
||||
const cursorLine = cursor.line;
|
||||
const start = dir < 0 ? 0 : cursorLine + 1;
|
||||
const end = dir < 0 ? cursorLine : doc.lineCount();
|
||||
let found = -1;
|
||||
doc.eachLine(start, end, lineHandle => {
|
||||
const markers = lineHandle.gutterMarkers || null;
|
||||
if ( markers === null ) { return; }
|
||||
const marker = markers['CodeMirror-lintgutter'];
|
||||
if ( marker === undefined ) { return; }
|
||||
if ( marker.dataset.error !== 'y' ) { return; }
|
||||
const line = lineHandle.lineNo();
|
||||
if ( dir < 0 ) {
|
||||
found = line;
|
||||
return;
|
||||
}
|
||||
found = line;
|
||||
return true;
|
||||
});
|
||||
if ( found === -1 || found === cursorLine ) { return; }
|
||||
cm.getDoc().setCursor(found);
|
||||
const { clientHeight } = cm.getScrollInfo();
|
||||
cm.scrollIntoView({ line: found, ch: 0 }, clientHeight >>> 1);
|
||||
};
|
||||
|
||||
const clearSearch = function(cm, hard) {
|
||||
cm.operation(function() {
|
||||
const state = getSearchState(cm);
|
||||
if ( state.query ) {
|
||||
state.query = state.queryText = null;
|
||||
}
|
||||
state.lines = [];
|
||||
if ( state.overlay !== undefined ) {
|
||||
cm.removeOverlay(state.overlay);
|
||||
state.overlay = undefined;
|
||||
}
|
||||
if ( state.annotate ) {
|
||||
state.annotate.clear();
|
||||
state.annotate = undefined;
|
||||
}
|
||||
state.widget.removeAttribute('data-query');
|
||||
if ( hard ) {
|
||||
state.panel.clear();
|
||||
state.panel = null;
|
||||
state.widget = null;
|
||||
cm.state.search = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const findCommit = function(cm, dir) {
|
||||
const state = getSearchState(cm);
|
||||
state.queryTimer.off();
|
||||
const queryText = queryTextFromSearchWidget(cm);
|
||||
if ( queryText === state.queryText ) { return; }
|
||||
state.queryText = queryText;
|
||||
if ( state.queryText === '' ) {
|
||||
clearSearch(cm);
|
||||
} else {
|
||||
cm.operation(function() {
|
||||
startSearch(cm, state);
|
||||
findNext(cm, dir);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const findCommand = function(cm) {
|
||||
let queryText = cm.getSelection() || undefined;
|
||||
if ( !queryText ) {
|
||||
const word = cm.findWordAt(cm.getCursor());
|
||||
queryText = cm.getRange(word.anchor, word.head);
|
||||
if ( /^\W|\W$/.test(queryText) ) {
|
||||
queryText = undefined;
|
||||
}
|
||||
cm.setCursor(word.anchor);
|
||||
}
|
||||
queryTextToSearchWidget(cm, queryText);
|
||||
findCommit(cm, 1);
|
||||
};
|
||||
|
||||
const findNextCommand = function(cm) {
|
||||
const state = getSearchState(cm);
|
||||
if ( state.query ) { return findNext(cm, 1); }
|
||||
};
|
||||
|
||||
const findPrevCommand = function(cm) {
|
||||
const state = getSearchState(cm);
|
||||
if ( state.query ) { return findNext(cm, -1); }
|
||||
};
|
||||
|
||||
{
|
||||
const searchWidgetTemplate = [
|
||||
'<div class="cm-search-widget-template" style="display:none;">',
|
||||
'<div class="cm-search-widget">',
|
||||
'<span class="cm-maximize"><svg viewBox="0 0 40 40"><path d="M4,16V4h12M24,4H36V16M4,24V36H16M36,24V36H24" /><path d="M14 2.5v12h-12M38 14h-12v-12M14 38v-12h-12M26 38v-12h12" /></svg></span> ',
|
||||
'<span class="cm-search-widget-input">',
|
||||
'<span class="searchfield">',
|
||||
'<input type="search" spellcheck="false" placeholder="">',
|
||||
'<span class="fa-icon">search</span>',
|
||||
'</span> ',
|
||||
'<span class="cm-search-widget-up cm-search-widget-button fa-icon">angle-up</span> ',
|
||||
'<span class="cm-search-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ',
|
||||
'<span class="cm-search-widget-count"></span>',
|
||||
'</span>',
|
||||
'<span class="cm-linter-widget" data-lint="0">',
|
||||
'<span class="cm-linter-widget-count"></span> ',
|
||||
'<span class="cm-linter-widget-up cm-search-widget-button fa-icon">angle-up</span> ',
|
||||
'<span class="cm-linter-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ',
|
||||
'</span>',
|
||||
'<span>',
|
||||
'<a class="fa-icon sourceURL" href>external-link</a>',
|
||||
'</span>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
].join('\n');
|
||||
const domParser = new DOMParser();
|
||||
const doc = domParser.parseFromString(searchWidgetTemplate, 'text/html');
|
||||
const widgetTemplate = document.adoptNode(doc.body.firstElementChild);
|
||||
document.body.appendChild(widgetTemplate);
|
||||
}
|
||||
|
||||
CodeMirror.commands.find = findCommand;
|
||||
CodeMirror.commands.findNext = findNextCommand;
|
||||
CodeMirror.commands.findPrev = findPrevCommand;
|
||||
|
||||
CodeMirror.defineInitHook(function(cm) {
|
||||
getSearchState(cm);
|
||||
cm.on('linterDone', details => {
|
||||
const linterWidget = qs$('.cm-linter-widget');
|
||||
const count = details.errorCount;
|
||||
if ( linterWidget.dataset.lint === `${count}` ) { return; }
|
||||
linterWidget.dataset.lint = `${count}`;
|
||||
dom.text(
|
||||
qs$(linterWidget, '.cm-linter-widget-count'),
|
||||
i18n$('linterMainReport').replace('{{count}}', count.toLocaleString())
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
239
uBlock0.chromium/js/codemirror/ubo-dynamic-filtering.js
Normal file
239
uBlock0.chromium/js/codemirror/ubo-dynamic-filtering.js
Normal file
@@ -0,0 +1,239 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
Copyright (C) 2019-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
|
||||
*/
|
||||
|
||||
/* global CodeMirror */
|
||||
|
||||
'use strict';
|
||||
|
||||
CodeMirror.defineMode('ubo-dynamic-filtering', ( ) => {
|
||||
|
||||
const validSwitches = new Set([
|
||||
'no-strict-blocking:',
|
||||
'no-popups:',
|
||||
'no-cosmetic-filtering:',
|
||||
'no-remote-fonts:',
|
||||
'no-large-media:',
|
||||
'no-csp-reports:',
|
||||
'no-scripting:',
|
||||
]);
|
||||
const validSwitcheStates = new Set([
|
||||
'true',
|
||||
'false',
|
||||
]);
|
||||
const validHnRuleTypes = new Set([
|
||||
'*',
|
||||
'3p',
|
||||
'image',
|
||||
'inline-script',
|
||||
'1p-script',
|
||||
'3p-script',
|
||||
'3p-frame',
|
||||
]);
|
||||
const invalidURLRuleTypes = new Set([
|
||||
'doc',
|
||||
'main_frame',
|
||||
]);
|
||||
const validActions = new Set([
|
||||
'block',
|
||||
'allow',
|
||||
'noop',
|
||||
]);
|
||||
const hnValidator = new URL(self.location.href);
|
||||
const reBadHn = /[%]|^\.|\.$/;
|
||||
const slices = [];
|
||||
let sliceIndex = 0;
|
||||
let sliceCount = 0;
|
||||
let hostnameToDomainMap = new Map();
|
||||
let psl;
|
||||
|
||||
const isValidHostname = hnin => {
|
||||
if ( hnin === '*' ) { return true; }
|
||||
hnValidator.hostname = '_';
|
||||
try {
|
||||
hnValidator.hostname = hnin;
|
||||
} catch(_) {
|
||||
return false;
|
||||
}
|
||||
const hnout = hnValidator.hostname;
|
||||
return hnout !== '_' && hnout !== '' && reBadHn.test(hnout) === false;
|
||||
};
|
||||
|
||||
const addSlice = (len, style = null) => {
|
||||
let i = sliceCount;
|
||||
if ( i === slices.length ) {
|
||||
slices[i] = { len: 0, style: null };
|
||||
}
|
||||
const entry = slices[i];
|
||||
entry.len = len;
|
||||
entry.style = style;
|
||||
sliceCount += 1;
|
||||
};
|
||||
|
||||
const addMatchSlice = (match, style = null) => {
|
||||
const len = match !== null ? match[0].length : 0;
|
||||
addSlice(len, style);
|
||||
return match !== null ? match.input.slice(len) : '';
|
||||
};
|
||||
|
||||
const addMatchHnSlices = (match, style = null) => {
|
||||
const hn = match[0];
|
||||
if ( hn === '*' ) {
|
||||
return addMatchSlice(match, style);
|
||||
}
|
||||
let dn = hostnameToDomainMap.get(hn) || '';
|
||||
if ( dn === '' && psl !== undefined ) {
|
||||
dn = /(\d|\])$/.test(hn) ? hn : (psl.getDomain(hn) || hn);
|
||||
}
|
||||
const entityBeg = hn.length - dn.length;
|
||||
if ( entityBeg !== 0 ) {
|
||||
addSlice(entityBeg, style);
|
||||
}
|
||||
let entityEnd = dn.indexOf('.');
|
||||
if ( entityEnd === -1 ) { entityEnd = dn.length; }
|
||||
addSlice(entityEnd, style !== null ? `${style} strong` : 'strong');
|
||||
if ( entityEnd < dn.length ) {
|
||||
addSlice(dn.length - entityEnd, style);
|
||||
}
|
||||
return match.input.slice(hn.length);
|
||||
};
|
||||
|
||||
const makeSlices = (stream, opts) => {
|
||||
sliceIndex = 0;
|
||||
sliceCount = 0;
|
||||
let { string } = stream;
|
||||
if ( string === '...' ) { return; }
|
||||
const { sortType } = opts;
|
||||
const reNotToken = /^\s+/;
|
||||
const reToken = /^\S+/;
|
||||
const tokens = [];
|
||||
// leading whitespaces
|
||||
let match = reNotToken.exec(string);
|
||||
if ( match !== null ) {
|
||||
string = addMatchSlice(match);
|
||||
}
|
||||
// first token
|
||||
match = reToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
tokens.push(match[0]);
|
||||
// hostname or switch
|
||||
const isSwitchRule = validSwitches.has(match[0]);
|
||||
if ( isSwitchRule ) {
|
||||
string = addMatchSlice(match, sortType === 0 ? 'sortkey' : null);
|
||||
} else if ( isValidHostname(match[0]) ) {
|
||||
if ( sortType === 1 ) {
|
||||
string = addMatchHnSlices(match, 'sortkey');
|
||||
} else {
|
||||
string = addMatchHnSlices(match, null);
|
||||
}
|
||||
} else {
|
||||
string = addMatchSlice(match, 'error');
|
||||
}
|
||||
// whitespaces before second token
|
||||
match = reNotToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
string = addMatchSlice(match);
|
||||
// second token
|
||||
match = reToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
tokens.push(match[0]);
|
||||
// hostname or url
|
||||
const isURLRule = isSwitchRule === false && match[0].indexOf('://') > 0;
|
||||
if ( isURLRule ) {
|
||||
string = addMatchSlice(match, sortType === 2 ? 'sortkey' : null);
|
||||
} else if ( isValidHostname(match[0]) === false ) {
|
||||
string = addMatchSlice(match, 'error');
|
||||
} else if ( sortType === 1 && isSwitchRule || sortType === 2 ) {
|
||||
string = addMatchHnSlices(match, 'sortkey');
|
||||
} else {
|
||||
string = addMatchHnSlices(match, null);
|
||||
}
|
||||
// whitespaces before third token
|
||||
match = reNotToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
string = addMatchSlice(match);
|
||||
// third token
|
||||
match = reToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
tokens.push(match[0]);
|
||||
// rule type or switch state
|
||||
if ( isSwitchRule ) {
|
||||
string = validSwitcheStates.has(match[0])
|
||||
? addMatchSlice(match, match[0] === 'true' ? 'blockrule' : 'allowrule')
|
||||
: addMatchSlice(match, 'error');
|
||||
} else if ( isURLRule ) {
|
||||
string = invalidURLRuleTypes.has(match[0])
|
||||
? addMatchSlice(match, 'error')
|
||||
: addMatchSlice(match);
|
||||
} else if ( tokens[1] === '*' ) {
|
||||
string = validHnRuleTypes.has(match[0])
|
||||
? addMatchSlice(match)
|
||||
: addMatchSlice(match, 'error');
|
||||
} else {
|
||||
string = match[0] === '*'
|
||||
? addMatchSlice(match)
|
||||
: addMatchSlice(match, 'error');
|
||||
}
|
||||
// whitespaces before fourth token
|
||||
match = reNotToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
string = addMatchSlice(match);
|
||||
// fourth token
|
||||
match = reToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
tokens.push(match[0]);
|
||||
string = isSwitchRule || validActions.has(match[0]) === false
|
||||
? addMatchSlice(match, 'error')
|
||||
: addMatchSlice(match, `${match[0]}rule`);
|
||||
// whitespaces before end of line
|
||||
match = reNotToken.exec(string);
|
||||
if ( match === null ) { return; }
|
||||
string = addMatchSlice(match);
|
||||
// any token beyond fourth token is invalid
|
||||
match = reToken.exec(string);
|
||||
if ( match !== null ) {
|
||||
string = addMatchSlice(null, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const token = function(stream) {
|
||||
if ( stream.sol() ) {
|
||||
makeSlices(stream, this);
|
||||
}
|
||||
if ( sliceIndex >= sliceCount ) {
|
||||
stream.skipToEnd(stream);
|
||||
return null;
|
||||
}
|
||||
const { len, style } = slices[sliceIndex++];
|
||||
if ( len === 0 ) {
|
||||
stream.skipToEnd();
|
||||
} else {
|
||||
stream.pos += len;
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
return {
|
||||
token,
|
||||
sortType: 1,
|
||||
setHostnameToDomainMap: a => { hostnameToDomainMap = a; },
|
||||
setPSL: a => { psl = a; },
|
||||
};
|
||||
});
|
||||
1220
uBlock0.chromium/js/codemirror/ubo-static-filtering.js
Normal file
1220
uBlock0.chromium/js/codemirror/ubo-static-filtering.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user