mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-01 08:39:37 +02:00
integrate codemirror into "My filters" pane
This commit is contained in:
parent
cff1bcb24f
commit
61ec088d43
@ -4,10 +4,15 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>uBlock — Your filters</title>
|
<title>uBlock — Your filters</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="lib/codemirror/addon/dialog/dialog.css">
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/1p-filters.css">
|
<link rel="stylesheet" type="text/css" href="css/1p-filters.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/codemirror.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -19,13 +24,19 @@
|
|||||||
<button id="userFiltersApply" class="custom important" type="button" disabled="true" data-i18n="1pApplyChanges"></button> 
|
<button id="userFiltersApply" class="custom important" type="button" disabled="true" data-i18n="1pApplyChanges"></button> 
|
||||||
<button id="userFiltersRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
|
<button id="userFiltersRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
|
||||||
</p>
|
</p>
|
||||||
<p><textarea class="userFilters" id="userFilters" dir="auto" spellcheck="false"></textarea></p>
|
<p><div class="codeMirrorContainer" id="userFilters" dir="auto" spellcheck="false"></div></p>
|
||||||
<p>
|
<p>
|
||||||
<button id="importUserFiltersFromFile" class="custom" data-i18n="1pImport"></button> 
|
<button id="importUserFiltersFromFile" class="custom" data-i18n="1pImport"></button> 
|
||||||
<button id="exportUserFiltersToFile" class="custom" data-i18n="1pExport"></button>
|
<button id="exportUserFiltersToFile" class="custom" data-i18n="1pExport"></button>
|
||||||
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput">
|
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/dialog/dialog.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/search.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/mode/ubo-static-filtering.js"></script>
|
||||||
|
|
||||||
<script src="js/vapi.js"></script>
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
|
@ -4,27 +4,29 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>uBlock — Asset</title>
|
<title>uBlock — Asset</title>
|
||||||
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
|
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="lib/codemirror/addon/dialog/dialog.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/codemirror.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
border: 0;
|
border: 0;
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
#content {
|
#content {
|
||||||
font: 12px monospace;
|
height: 100vh;
|
||||||
height: 100%;
|
width: 100vw;
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
.CodeMirror {
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="content"></div>
|
<div id="content" class="codeMirrorContainer"></div>
|
||||||
|
|
||||||
<script src="lib/codemirror/lib/codemirror.js"></script>
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/dialog/dialog.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/search.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/mode/ubo-static-filtering.js"></script>
|
||||||
|
|
||||||
<script src="js/vapi.js"></script>
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
|
@ -4,11 +4,9 @@ div > p:first-child {
|
|||||||
div > p:last-child {
|
div > p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.userFilters {
|
#userFilters {
|
||||||
box-sizing: border-box;
|
|
||||||
height: 60vh;
|
height: 60vh;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: pre;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
}
|
}
|
||||||
|
10
src/css/codemirror.css
Normal file
10
src/css/codemirror.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.codeMirrorContainer {
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.CodeMirror {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uBlock Origin - a browser extension to block requests.
|
uBlock Origin - a browser extension to block requests.
|
||||||
Copyright (C) 2014-2016 Raymond Hill
|
Copyright (C) 2014-2018 Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -19,26 +19,34 @@
|
|||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global uDom, uBlockDashboard */
|
/* global CodeMirror, uDom, uBlockDashboard */
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
var messaging = vAPI.messaging;
|
var messaging = vAPI.messaging;
|
||||||
var cachedUserFilters = '';
|
var cachedUserFilters = '';
|
||||||
|
|
||||||
|
var cmEditor = new CodeMirror(
|
||||||
|
document.getElementById('userFilters'),
|
||||||
|
{
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// This is to give a visual hint that the content of user blacklist has changed.
|
// This is to give a visual hint that the content of user blacklist has changed.
|
||||||
|
|
||||||
function userFiltersChanged(changed) {
|
function userFiltersChanged(changed) {
|
||||||
if ( typeof changed !== 'boolean' ) {
|
if ( typeof changed !== 'boolean' ) {
|
||||||
changed = uDom.nodeFromId('userFilters').value.trim() !== cachedUserFilters;
|
changed = cmEditor.getValue().trim() !== cachedUserFilters;
|
||||||
}
|
}
|
||||||
uDom.nodeFromId('userFiltersApply').disabled = !changed;
|
uDom.nodeFromId('userFiltersApply').disabled = !changed;
|
||||||
uDom.nodeFromId('userFiltersRevert').disabled = !changed;
|
uDom.nodeFromId('userFiltersRevert').disabled = !changed;
|
||||||
@ -49,14 +57,14 @@ function userFiltersChanged(changed) {
|
|||||||
function renderUserFilters(first) {
|
function renderUserFilters(first) {
|
||||||
var onRead = function(details) {
|
var onRead = function(details) {
|
||||||
if ( details.error ) { return; }
|
if ( details.error ) { return; }
|
||||||
var textarea = uDom.nodeFromId('userFilters');
|
var content = details.content.trim();
|
||||||
cachedUserFilters = details.content.trim();
|
cachedUserFilters = content;
|
||||||
textarea.value = details.content;
|
if ( content.length !== 0 ) {
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
cmEditor.setValue(content);
|
||||||
if ( first ) {
|
if ( first ) {
|
||||||
textarea.value += '\n';
|
cmEditor.clearHistory();
|
||||||
var textlen = textarea.value.length;
|
|
||||||
textarea.setSelectionRange(textlen, textlen);
|
|
||||||
textarea.focus();
|
|
||||||
}
|
}
|
||||||
userFiltersChanged(false);
|
userFiltersChanged(false);
|
||||||
};
|
};
|
||||||
@ -146,35 +154,33 @@ var exportUserFiltersToFile = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var applyChanges = function() {
|
var applyChanges = function() {
|
||||||
var textarea = uDom.nodeFromId('userFilters');
|
|
||||||
|
|
||||||
var onWritten = function(details) {
|
var onWritten = function(details) {
|
||||||
if ( details.error ) {
|
if ( details.error ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
textarea.value = details.content;
|
|
||||||
cachedUserFilters = details.content.trim();
|
cachedUserFilters = details.content.trim();
|
||||||
userFiltersChanged();
|
userFiltersChanged();
|
||||||
allFiltersApplyHandler();
|
allFiltersApplyHandler();
|
||||||
textarea.focus();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = {
|
var request = {
|
||||||
what: 'writeUserFilters',
|
what: 'writeUserFilters',
|
||||||
content: textarea.value
|
content: cmEditor.getValue()
|
||||||
};
|
};
|
||||||
messaging.send('dashboard', request, onWritten);
|
messaging.send('dashboard', request, onWritten);
|
||||||
};
|
};
|
||||||
|
|
||||||
var revertChanges = function() {
|
var revertChanges = function() {
|
||||||
uDom.nodeFromId('userFilters').value = cachedUserFilters + '\n';
|
var content = cachedUserFilters;
|
||||||
|
if ( content.length !== 0 ) {
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
cmEditor.setValue(content);
|
||||||
userFiltersChanged();
|
userFiltersChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var getCloudData = function() {
|
var getCloudData = function() {
|
||||||
return uDom.nodeFromId('userFilters').value;
|
return cmEditor.getValue();
|
||||||
};
|
};
|
||||||
|
|
||||||
var setCloudData = function(data, append) {
|
var setCloudData = function(data, append) {
|
||||||
@ -185,7 +191,7 @@ var setCloudData = function(data, append) {
|
|||||||
if ( append ) {
|
if ( append ) {
|
||||||
data = uBlockDashboard.mergeNewLines(textarea.value, data);
|
data = uBlockDashboard.mergeNewLines(textarea.value, data);
|
||||||
}
|
}
|
||||||
textarea.value = data;
|
cmEditor.setValue(data);
|
||||||
userFiltersChanged();
|
userFiltersChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -198,12 +204,13 @@ self.cloud.onPull = setCloudData;
|
|||||||
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
|
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
|
||||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||||
uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile);
|
uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile);
|
||||||
uDom('#userFilters').on('input', userFiltersChanged);
|
|
||||||
uDom('#userFiltersApply').on('click', applyChanges);
|
uDom('#userFiltersApply').on('click', applyChanges);
|
||||||
uDom('#userFiltersRevert').on('click', revertChanges);
|
uDom('#userFiltersRevert').on('click', revertChanges);
|
||||||
|
|
||||||
renderUserFilters(true);
|
renderUserFilters(true);
|
||||||
|
|
||||||
|
cmEditor.on('changes', userFiltersChanged);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=UNilsLf6eW4
|
// https://www.youtube.com/watch?v=UNilsLf6eW4
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
document.getElementById('content'),
|
document.getElementById('content'),
|
||||||
{
|
{
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
readOnly: true
|
readOnly: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
32
src/lib/codemirror/addon/dialog/dialog.css
vendored
Normal file
32
src/lib/codemirror/addon/dialog/dialog.css
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.CodeMirror-dialog {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0;
|
||||||
|
background: inherit;
|
||||||
|
z-index: 15;
|
||||||
|
padding: .1em .8em;
|
||||||
|
overflow: hidden;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog-top {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog-bottom {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog input {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
width: 20em;
|
||||||
|
color: inherit;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog button {
|
||||||
|
font-size: 70%;
|
||||||
|
}
|
157
src/lib/codemirror/addon/dialog/dialog.js
vendored
Normal file
157
src/lib/codemirror/addon/dialog/dialog.js
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
// Open simple dialogs on top of an editor. Relies on dialog.css.
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
function dialogDiv(cm, template, bottom) {
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
var dialog;
|
||||||
|
dialog = wrap.appendChild(document.createElement("div"));
|
||||||
|
if (bottom)
|
||||||
|
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
|
||||||
|
else
|
||||||
|
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
|
||||||
|
|
||||||
|
if (typeof template == "string") {
|
||||||
|
dialog.innerHTML = template;
|
||||||
|
} else { // Assuming it's a detached DOM element.
|
||||||
|
dialog.appendChild(template);
|
||||||
|
}
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeNotification(cm, newVal) {
|
||||||
|
if (cm.state.currentNotificationClose)
|
||||||
|
cm.state.currentNotificationClose();
|
||||||
|
cm.state.currentNotificationClose = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
|
||||||
|
if (!options) options = {};
|
||||||
|
|
||||||
|
closeNotification(this, null);
|
||||||
|
|
||||||
|
var dialog = dialogDiv(this, template, options.bottom);
|
||||||
|
var closed = false, me = this;
|
||||||
|
function close(newVal) {
|
||||||
|
if (typeof newVal == 'string') {
|
||||||
|
inp.value = newVal;
|
||||||
|
} else {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
dialog.parentNode.removeChild(dialog);
|
||||||
|
me.focus();
|
||||||
|
|
||||||
|
if (options.onClose) options.onClose(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inp = dialog.getElementsByTagName("input")[0], button;
|
||||||
|
if (inp) {
|
||||||
|
inp.focus();
|
||||||
|
|
||||||
|
if (options.value) {
|
||||||
|
inp.value = options.value;
|
||||||
|
if (options.selectValueOnOpen !== false) {
|
||||||
|
inp.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.onInput)
|
||||||
|
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
||||||
|
if (options.onKeyUp)
|
||||||
|
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
|
||||||
|
|
||||||
|
CodeMirror.on(inp, "keydown", function(e) {
|
||||||
|
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
|
||||||
|
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
|
||||||
|
inp.blur();
|
||||||
|
CodeMirror.e_stop(e);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
if (e.keyCode == 13) callback(inp.value, e);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
||||||
|
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
||||||
|
CodeMirror.on(button, "click", function() {
|
||||||
|
close();
|
||||||
|
me.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
|
||||||
|
|
||||||
|
button.focus();
|
||||||
|
}
|
||||||
|
return close;
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
|
||||||
|
closeNotification(this, null);
|
||||||
|
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||||
|
var buttons = dialog.getElementsByTagName("button");
|
||||||
|
var closed = false, me = this, blurring = 1;
|
||||||
|
function close() {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
dialog.parentNode.removeChild(dialog);
|
||||||
|
me.focus();
|
||||||
|
}
|
||||||
|
buttons[0].focus();
|
||||||
|
for (var i = 0; i < buttons.length; ++i) {
|
||||||
|
var b = buttons[i];
|
||||||
|
(function(callback) {
|
||||||
|
CodeMirror.on(b, "click", function(e) {
|
||||||
|
CodeMirror.e_preventDefault(e);
|
||||||
|
close();
|
||||||
|
if (callback) callback(me);
|
||||||
|
});
|
||||||
|
})(callbacks[i]);
|
||||||
|
CodeMirror.on(b, "blur", function() {
|
||||||
|
--blurring;
|
||||||
|
setTimeout(function() { if (blurring <= 0) close(); }, 200);
|
||||||
|
});
|
||||||
|
CodeMirror.on(b, "focus", function() { ++blurring; });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* openNotification
|
||||||
|
* Opens a notification, that can be closed with an optional timer
|
||||||
|
* (default 5000ms timer) and always closes on click.
|
||||||
|
*
|
||||||
|
* If a notification is opened while another is opened, it will close the
|
||||||
|
* currently opened one and open the new one immediately.
|
||||||
|
*/
|
||||||
|
CodeMirror.defineExtension("openNotification", function(template, options) {
|
||||||
|
closeNotification(this, close);
|
||||||
|
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||||
|
var closed = false, doneTimer;
|
||||||
|
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
clearTimeout(doneTimer);
|
||||||
|
dialog.parentNode.removeChild(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.on(dialog, 'click', function(e) {
|
||||||
|
CodeMirror.e_preventDefault(e);
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (duration)
|
||||||
|
doneTimer = setTimeout(close, duration);
|
||||||
|
|
||||||
|
return close;
|
||||||
|
});
|
||||||
|
});
|
49
src/lib/codemirror/addon/mode/ubo-static-filtering.js
vendored
Normal file
49
src/lib/codemirror/addon/mode/ubo-static-filtering.js
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2018 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-static-filtering", function() {
|
||||||
|
return {
|
||||||
|
token: function(stream) {
|
||||||
|
if ( stream.sol() === false ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
stream.eatSpace();
|
||||||
|
var c = stream.peek();
|
||||||
|
if ( c === '!' ) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return 'comment';
|
||||||
|
}
|
||||||
|
if ( c === '#' ) {
|
||||||
|
c = stream.peek();
|
||||||
|
if ( c !== '#' && c !== '@' ) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return 'comment';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.skipToEnd();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
252
src/lib/codemirror/addon/search/search.js
vendored
Normal file
252
src/lib/codemirror/addon/search/search.js
vendored
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
(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) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
return {token: function(stream) {
|
||||||
|
query.lastIndex = stream.pos;
|
||||||
|
var 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();
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchState() {
|
||||||
|
this.posFrom = this.posTo = this.lastQuery = this.query = null;
|
||||||
|
this.overlay = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchState(cm) {
|
||||||
|
return cm.state.search || (cm.state.search = new SearchState());
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryCaseInsensitive(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.
|
||||||
|
return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
|
||||||
|
cm.openDialog(text, onEnter, {
|
||||||
|
value: deflt,
|
||||||
|
selectValueOnOpen: true,
|
||||||
|
closeOnEnter: false,
|
||||||
|
onClose: function() { clearSearch(cm); },
|
||||||
|
onKeyDown: onKeyDown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function dialog(cm, text, shortText, deflt, f) {
|
||||||
|
if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
|
||||||
|
else f(prompt(shortText, deflt));
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDialog(cm, text, shortText, fs) {
|
||||||
|
if (cm.openConfirm) cm.openConfirm(text, fs);
|
||||||
|
else if (confirm(shortText)) fs[0]();
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseString(string) {
|
||||||
|
return string.replace(/\\(.)/g, function(_, ch) {
|
||||||
|
if (ch == "n") return "\n"
|
||||||
|
if (ch == "r") return "\r"
|
||||||
|
return ch
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if (typeof query == "string" ? query == "" : query.test(""))
|
||||||
|
query = /x^/;
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryDialog =
|
||||||
|
'<span class="CodeMirror-search-label">Search:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
|
||||||
|
|
||||||
|
function startSearch(cm, state, query) {
|
||||||
|
state.queryText = query;
|
||||||
|
state.query = parseQuery(query);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSearch(cm, rev, persistent, immediate) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if (state.query) return findNext(cm, rev);
|
||||||
|
var q = cm.getSelection() || state.lastQuery;
|
||||||
|
if (q instanceof RegExp && q.source == "x^") q = null
|
||||||
|
if (persistent && cm.openDialog) {
|
||||||
|
var hiding = null
|
||||||
|
var searchNext = function(query, event) {
|
||||||
|
CodeMirror.e_stop(event);
|
||||||
|
if (!query) return;
|
||||||
|
if (query != state.queryText) {
|
||||||
|
startSearch(cm, state, query);
|
||||||
|
state.posFrom = state.posTo = cm.getCursor();
|
||||||
|
}
|
||||||
|
if (hiding) hiding.style.opacity = 1
|
||||||
|
findNext(cm, event.shiftKey, function(_, to) {
|
||||||
|
var dialog
|
||||||
|
if (to.line < 3 && document.querySelector &&
|
||||||
|
(dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
|
||||||
|
dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
|
||||||
|
(hiding = dialog).style.opacity = .4
|
||||||
|
})
|
||||||
|
};
|
||||||
|
persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
|
||||||
|
var keyName = CodeMirror.keyName(event)
|
||||||
|
var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
|
||||||
|
if (cmd == "findNext" || cmd == "findPrev" ||
|
||||||
|
cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
|
||||||
|
CodeMirror.e_stop(event);
|
||||||
|
startSearch(cm, getSearchState(cm), query);
|
||||||
|
cm.execCommand(cmd);
|
||||||
|
} else if (cmd == "find" || cmd == "findPersistent") {
|
||||||
|
CodeMirror.e_stop(event);
|
||||||
|
searchNext(query, event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (immediate && q) {
|
||||||
|
startSearch(cm, state, q);
|
||||||
|
findNext(cm, rev);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dialog(cm, queryDialog, "Search for:", q, function(query) {
|
||||||
|
if (query && !state.query) cm.operation(function() {
|
||||||
|
startSearch(cm, state, query);
|
||||||
|
state.posFrom = state.posTo = cm.getCursor();
|
||||||
|
findNext(cm, rev);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNext(cm, rev, callback) {cm.operation(function() {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
||||||
|
if (!cursor.find(rev)) {
|
||||||
|
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
|
||||||
|
if (!cursor.find(rev)) return;
|
||||||
|
}
|
||||||
|
cm.setSelection(cursor.from(), cursor.to());
|
||||||
|
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
|
||||||
|
state.posFrom = cursor.from(); state.posTo = cursor.to();
|
||||||
|
if (callback) callback(cursor.from(), cursor.to())
|
||||||
|
});}
|
||||||
|
|
||||||
|
function clearSearch(cm) {cm.operation(function() {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
state.lastQuery = state.query;
|
||||||
|
if (!state.query) return;
|
||||||
|
state.query = state.queryText = null;
|
||||||
|
cm.removeOverlay(state.overlay);
|
||||||
|
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
|
||||||
|
});}
|
||||||
|
|
||||||
|
var replaceQueryDialog =
|
||||||
|
' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
|
||||||
|
var replacementQueryDialog = '<span class="CodeMirror-search-label">With:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
|
||||||
|
var doReplaceConfirm = '<span class="CodeMirror-search-label">Replace?</span> <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>';
|
||||||
|
|
||||||
|
function replaceAll(cm, query, text) {
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
|
||||||
|
if (typeof query != "string") {
|
||||||
|
var match = cm.getRange(cursor.from(), cursor.to()).match(query);
|
||||||
|
cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
|
||||||
|
} else cursor.replace(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replace(cm, all) {
|
||||||
|
if (cm.getOption("readOnly")) return;
|
||||||
|
var query = cm.getSelection() || getSearchState(cm).lastQuery;
|
||||||
|
var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Replace all:' : 'Replace:') + '</span>';
|
||||||
|
dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
|
||||||
|
if (!query) return;
|
||||||
|
query = parseQuery(query);
|
||||||
|
dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
|
||||||
|
text = parseString(text)
|
||||||
|
if (all) {
|
||||||
|
replaceAll(cm, query, text)
|
||||||
|
} else {
|
||||||
|
clearSearch(cm);
|
||||||
|
var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
|
||||||
|
var advance = function() {
|
||||||
|
var start = cursor.from(), match;
|
||||||
|
if (!(match = cursor.findNext())) {
|
||||||
|
cursor = getSearchCursor(cm, query);
|
||||||
|
if (!(match = cursor.findNext()) ||
|
||||||
|
(start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
|
||||||
|
}
|
||||||
|
cm.setSelection(cursor.from(), cursor.to());
|
||||||
|
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
|
||||||
|
confirmDialog(cm, doReplaceConfirm, "Replace?",
|
||||||
|
[function() {doReplace(match);}, advance,
|
||||||
|
function() {replaceAll(cm, query, text)}]);
|
||||||
|
};
|
||||||
|
var doReplace = function(match) {
|
||||||
|
cursor.replace(typeof query == "string" ? text :
|
||||||
|
text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
|
||||||
|
advance();
|
||||||
|
};
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
|
||||||
|
CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
|
||||||
|
CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
|
||||||
|
CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
|
||||||
|
CodeMirror.commands.findNext = doSearch;
|
||||||
|
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
|
||||||
|
CodeMirror.commands.clearSearch = clearSearch;
|
||||||
|
CodeMirror.commands.replace = replace;
|
||||||
|
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
|
||||||
|
});
|
293
src/lib/codemirror/addon/search/searchcursor.js
vendored
Normal file
293
src/lib/codemirror/addon/search/searchcursor.js
vendored
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
// 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"))
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod)
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror)
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict"
|
||||||
|
var Pos = CodeMirror.Pos
|
||||||
|
|
||||||
|
function regexpFlags(regexp) {
|
||||||
|
var flags = regexp.flags
|
||||||
|
return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
|
||||||
|
+ (regexp.global ? "g" : "")
|
||||||
|
+ (regexp.multiline ? "m" : "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureFlags(regexp, flags) {
|
||||||
|
var current = regexpFlags(regexp), target = current
|
||||||
|
for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
|
||||||
|
target += flags.charAt(i)
|
||||||
|
return current == target ? regexp : new RegExp(regexp.source, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeMultiline(regexp) {
|
||||||
|
return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpForward(doc, regexp, start) {
|
||||||
|
regexp = ensureFlags(regexp, "g")
|
||||||
|
for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
|
||||||
|
regexp.lastIndex = ch
|
||||||
|
var string = doc.getLine(line), match = regexp.exec(string)
|
||||||
|
if (match)
|
||||||
|
return {from: Pos(line, match.index),
|
||||||
|
to: Pos(line, match.index + match[0].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpForwardMultiline(doc, regexp, start) {
|
||||||
|
if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
|
||||||
|
|
||||||
|
regexp = ensureFlags(regexp, "gm")
|
||||||
|
var string, chunk = 1
|
||||||
|
for (var line = start.line, last = doc.lastLine(); line <= last;) {
|
||||||
|
// This grows the search buffer in exponentially-sized chunks
|
||||||
|
// between matches, so that nearby matches are fast and don't
|
||||||
|
// require concatenating the whole document (in case we're
|
||||||
|
// searching for something that has tons of matches), but at the
|
||||||
|
// same time, the amount of retries is limited.
|
||||||
|
for (var i = 0; i < chunk; i++) {
|
||||||
|
if (line > last) break
|
||||||
|
var curLine = doc.getLine(line++)
|
||||||
|
string = string == null ? curLine : string + "\n" + curLine
|
||||||
|
}
|
||||||
|
chunk = chunk * 2
|
||||||
|
regexp.lastIndex = start.ch
|
||||||
|
var match = regexp.exec(string)
|
||||||
|
if (match) {
|
||||||
|
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
||||||
|
var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
|
||||||
|
return {from: Pos(startLine, startCh),
|
||||||
|
to: Pos(startLine + inside.length - 1,
|
||||||
|
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lastMatchIn(string, regexp) {
|
||||||
|
var cutOff = 0, match
|
||||||
|
for (;;) {
|
||||||
|
regexp.lastIndex = cutOff
|
||||||
|
var newMatch = regexp.exec(string)
|
||||||
|
if (!newMatch) return match
|
||||||
|
match = newMatch
|
||||||
|
cutOff = match.index + (match[0].length || 1)
|
||||||
|
if (cutOff == string.length) return match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpBackward(doc, regexp, start) {
|
||||||
|
regexp = ensureFlags(regexp, "g")
|
||||||
|
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
|
||||||
|
var string = doc.getLine(line)
|
||||||
|
if (ch > -1) string = string.slice(0, ch)
|
||||||
|
var match = lastMatchIn(string, regexp)
|
||||||
|
if (match)
|
||||||
|
return {from: Pos(line, match.index),
|
||||||
|
to: Pos(line, match.index + match[0].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpBackwardMultiline(doc, regexp, start) {
|
||||||
|
regexp = ensureFlags(regexp, "gm")
|
||||||
|
var string, chunk = 1
|
||||||
|
for (var line = start.line, first = doc.firstLine(); line >= first;) {
|
||||||
|
for (var i = 0; i < chunk; i++) {
|
||||||
|
var curLine = doc.getLine(line--)
|
||||||
|
string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
|
||||||
|
}
|
||||||
|
chunk *= 2
|
||||||
|
|
||||||
|
var match = lastMatchIn(string, regexp)
|
||||||
|
if (match) {
|
||||||
|
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
||||||
|
var startLine = line + before.length, startCh = before[before.length - 1].length
|
||||||
|
return {from: Pos(startLine, startCh),
|
||||||
|
to: Pos(startLine + inside.length - 1,
|
||||||
|
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var doFold, noFold
|
||||||
|
if (String.prototype.normalize) {
|
||||||
|
doFold = function(str) { return str.normalize("NFD").toLowerCase() }
|
||||||
|
noFold = function(str) { return str.normalize("NFD") }
|
||||||
|
} else {
|
||||||
|
doFold = function(str) { return str.toLowerCase() }
|
||||||
|
noFold = function(str) { return str }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps a position in a case-folded line back to a position in the original line
|
||||||
|
// (compensating for codepoints increasing in number during folding)
|
||||||
|
function adjustPos(orig, folded, pos, foldFunc) {
|
||||||
|
if (orig.length == folded.length) return pos
|
||||||
|
for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
|
||||||
|
if (min == max) return min
|
||||||
|
var mid = (min + max) >> 1
|
||||||
|
var len = foldFunc(orig.slice(0, mid)).length
|
||||||
|
if (len == pos) return mid
|
||||||
|
else if (len > pos) max = mid
|
||||||
|
else min = mid + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchStringForward(doc, query, start, caseFold) {
|
||||||
|
// Empty string would match anything and never progress, so we
|
||||||
|
// define it to match nothing instead.
|
||||||
|
if (!query.length) return null
|
||||||
|
var fold = caseFold ? doFold : noFold
|
||||||
|
var lines = fold(query).split(/\r|\n\r?/)
|
||||||
|
|
||||||
|
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
|
||||||
|
var orig = doc.getLine(line).slice(ch), string = fold(orig)
|
||||||
|
if (lines.length == 1) {
|
||||||
|
var found = string.indexOf(lines[0])
|
||||||
|
if (found == -1) continue search
|
||||||
|
var start = adjustPos(orig, string, found, fold) + ch
|
||||||
|
return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
|
||||||
|
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
|
||||||
|
} else {
|
||||||
|
var cutFrom = string.length - lines[0].length
|
||||||
|
if (string.slice(cutFrom) != lines[0]) continue search
|
||||||
|
for (var i = 1; i < lines.length - 1; i++)
|
||||||
|
if (fold(doc.getLine(line + i)) != lines[i]) continue search
|
||||||
|
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
|
||||||
|
if (endString.slice(0, lastLine.length) != lastLine) continue search
|
||||||
|
return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
|
||||||
|
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchStringBackward(doc, query, start, caseFold) {
|
||||||
|
if (!query.length) return null
|
||||||
|
var fold = caseFold ? doFold : noFold
|
||||||
|
var lines = fold(query).split(/\r|\n\r?/)
|
||||||
|
|
||||||
|
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
|
||||||
|
var orig = doc.getLine(line)
|
||||||
|
if (ch > -1) orig = orig.slice(0, ch)
|
||||||
|
var string = fold(orig)
|
||||||
|
if (lines.length == 1) {
|
||||||
|
var found = string.lastIndexOf(lines[0])
|
||||||
|
if (found == -1) continue search
|
||||||
|
return {from: Pos(line, adjustPos(orig, string, found, fold)),
|
||||||
|
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
|
||||||
|
} else {
|
||||||
|
var lastLine = lines[lines.length - 1]
|
||||||
|
if (string.slice(0, lastLine.length) != lastLine) continue search
|
||||||
|
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
|
||||||
|
if (fold(doc.getLine(start + i)) != lines[i]) continue search
|
||||||
|
var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
|
||||||
|
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
|
||||||
|
return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
|
||||||
|
to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchCursor(doc, query, pos, options) {
|
||||||
|
this.atOccurrence = false
|
||||||
|
this.doc = doc
|
||||||
|
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
|
||||||
|
this.pos = {from: pos, to: pos}
|
||||||
|
|
||||||
|
var caseFold
|
||||||
|
if (typeof options == "object") {
|
||||||
|
caseFold = options.caseFold
|
||||||
|
} else { // Backwards compat for when caseFold was the 4th argument
|
||||||
|
caseFold = options
|
||||||
|
options = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof query == "string") {
|
||||||
|
if (caseFold == null) caseFold = false
|
||||||
|
this.matches = function(reverse, pos) {
|
||||||
|
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query = ensureFlags(query, "gm")
|
||||||
|
if (!options || options.multiline !== false)
|
||||||
|
this.matches = function(reverse, pos) {
|
||||||
|
return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.matches = function(reverse, pos) {
|
||||||
|
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchCursor.prototype = {
|
||||||
|
findNext: function() {return this.find(false)},
|
||||||
|
findPrevious: function() {return this.find(true)},
|
||||||
|
|
||||||
|
find: function(reverse) {
|
||||||
|
var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
|
||||||
|
|
||||||
|
// Implements weird auto-growing behavior on null-matches for
|
||||||
|
// backwards-compatiblity with the vim code (unfortunately)
|
||||||
|
while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
|
||||||
|
if (reverse) {
|
||||||
|
if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
|
||||||
|
else if (result.from.line == this.doc.firstLine()) result = null
|
||||||
|
else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
|
||||||
|
} else {
|
||||||
|
if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
|
||||||
|
else if (result.to.line == this.doc.lastLine()) result = null
|
||||||
|
else result = this.matches(reverse, Pos(result.to.line + 1, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
this.pos = result
|
||||||
|
this.atOccurrence = true
|
||||||
|
return this.pos.match || true
|
||||||
|
} else {
|
||||||
|
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
|
||||||
|
this.pos = {from: end, to: end}
|
||||||
|
return this.atOccurrence = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
from: function() {if (this.atOccurrence) return this.pos.from},
|
||||||
|
to: function() {if (this.atOccurrence) return this.pos.to},
|
||||||
|
|
||||||
|
replace: function(newText, origin) {
|
||||||
|
if (!this.atOccurrence) return
|
||||||
|
var lines = CodeMirror.splitLines(newText)
|
||||||
|
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
|
||||||
|
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
|
||||||
|
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||||
|
return new SearchCursor(this.doc, query, pos, caseFold)
|
||||||
|
})
|
||||||
|
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||||
|
return new SearchCursor(this, query, pos, caseFold)
|
||||||
|
})
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
|
||||||
|
var ranges = []
|
||||||
|
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
|
||||||
|
while (cur.findNext()) {
|
||||||
|
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
|
||||||
|
ranges.push({anchor: cur.from(), head: cur.to()})
|
||||||
|
}
|
||||||
|
if (ranges.length)
|
||||||
|
this.setSelections(ranges, 0)
|
||||||
|
})
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user