diff --git a/src/css/dyna-rules.css b/src/css/dyna-rules.css
index cf1ba0bdf..bab91c51a 100644
--- a/src/css/dyna-rules.css
+++ b/src/css/dyna-rules.css
@@ -34,7 +34,7 @@ body {
font-weight: normal;
margin: 0.5em 0;
}
-#diff .ruleFilter {
+#ruleFilter {
text-align: center;
}
body[dir="ltr"] #revertButton:after {
diff --git a/src/dyna-rules.html b/src/dyna-rules.html
index b9e156ce3..7bd278892 100644
--- a/src/dyna-rules.html
+++ b/src/dyna-rules.html
@@ -5,7 +5,6 @@
uBlock — Dynamic filtering rules
-
@@ -33,7 +32,7 @@
-
+
diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js
index e6a9627d6..a0c561adf 100644
--- a/src/js/codemirror/search.js
+++ b/src/js/codemirror/search.js
@@ -53,7 +53,7 @@
var searchWidgetHtml =
'' +
- ' ' +
+ ' ' +
'' +
'' +
'' +
diff --git a/src/js/dyna-rules.js b/src/js/dyna-rules.js
index ec2408f15..ba29b816b 100644
--- a/src/js/dyna-rules.js
+++ b/src/js/dyna-rules.js
@@ -36,6 +36,7 @@ var mergeView = new CodeMirror.MergeView(
{
allowEditingOriginals: true,
connect: 'align',
+ inputStyle: 'contenteditable',
lineNumbers: true,
lineWrapping: false,
origLeft: '',
@@ -47,46 +48,111 @@ mergeView.editor().setOption('styleActiveLine', true);
mergeView.editor().setOption('lineNumbers', false);
mergeView.leftOriginal().setOption('readOnly', 'nocursor');
-var cleanToken = 0;
+var unfilteredRules = {
+ orig: { doc: mergeView.leftOriginal(), rules: [] },
+ edit: { doc: mergeView.editor(), rules: [] }
+};
+
+var cleanEditToken = 0;
var cleanEditText = '';
var differ;
/******************************************************************************/
+// Borrowed from...
+// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js#L22
+// ... and modified as needed.
+
+var updateOverlay = (function() {
+ var reFilter;
+ var mode = {
+ token: function(stream) {
+ if ( reFilter !== undefined ) {
+ reFilter.lastIndex = stream.pos;
+ var match = reFilter.exec(stream.string);
+ if ( match !== null ) {
+ if ( match.index === stream.pos ) {
+ stream.pos += match[0].length || 1;
+ return 'searching';
+ }
+ stream.pos = match.index;
+ return;
+ }
+ }
+ stream.skipToEnd();
+ }
+ };
+ return function(filter) {
+ reFilter = typeof filter === 'string' && filter !== '' ?
+ new RegExp(filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi') :
+ undefined;
+ return mode;
+ };
+})();
+
+/******************************************************************************/
+
// Incrementally update text in a CodeMirror editor for best user experience:
// - Scroll position preserved
// - Minimum amount of text updated
-var rulesToDoc = function(doc, rules) {
- if ( doc.getValue() === '' || rules.length === 0 ) {
- doc.setValue(rules.length !== 0 ? rules.join('\n') : '');
- return;
- }
- if ( differ === undefined ) { differ = new diff_match_patch(); }
- var beforeText = doc.getValue();
- var afterText = rules.join('\n');
- var diffs = differ.diff_main(beforeText, afterText);
- doc.startOperation();
- var i = diffs.length,
- iedit = beforeText.length;
- while ( i-- ) {
- var diff = diffs[i];
- if ( diff[0] === 0 ) {
+var rulesToDoc = function(clearHistory) {
+ for ( var key in unfilteredRules ) {
+ if ( unfilteredRules.hasOwnProperty(key) === false ) { continue; }
+ var doc = unfilteredRules[key].doc;
+ var rules = filterRules(key);
+ if ( doc.lineCount() === 1 && doc.getValue() === '' || rules.length === 0 ) {
+ doc.setValue(rules.length !== 0 ? rules.join('\n') : '');
+ continue;
+ }
+ if ( differ === undefined ) { differ = new diff_match_patch(); }
+ var beforeText = doc.getValue();
+ var afterText = rules.join('\n');
+ var diffs = differ.diff_main(beforeText, afterText);
+ doc.startOperation();
+ var i = diffs.length,
+ iedit = beforeText.length;
+ while ( i-- ) {
+ var diff = diffs[i];
+ if ( diff[0] === 0 ) {
+ iedit -= diff[1].length;
+ continue;
+ }
+ var end = doc.posFromIndex(iedit);
+ if ( diff[0] === 1 ) {
+ doc.replaceRange(diff[1], end, end);
+ continue;
+ }
+ /* diff[0] === -1 */
iedit -= diff[1].length;
- continue;
+ var beg = doc.posFromIndex(iedit);
+ doc.replaceRange('', beg, end);
}
- var end = doc.posFromIndex(iedit);
- if ( diff[0] === 1 ) {
- doc.replaceRange(diff[1], end, end);
- continue;
- }
- /* diff[0] === -1 */
- iedit -= diff[1].length;
- var beg = doc.posFromIndex(iedit);
- doc.replaceRange('', beg, end);
+ doc.endOperation();
}
- doc.endOperation();
+ cleanEditText = mergeView.editor().getValue().trim();
+ cleanEditToken = mergeView.editor().changeGeneration();
+ if ( clearHistory ) {
+ mergeView.editor().clearHistory();
+ }
+};
+
+/******************************************************************************/
+
+var filterRules = function(key) {
+ var rules = unfilteredRules[key].rules;
+ var filter = uDom('#ruleFilter input').val();
+ if ( filter !== '' ) {
+ rules = rules.slice();
+ var i = rules.length;
+ while ( i-- ) {
+ if ( rules[i].indexOf(filter) === -1 ) {
+ rules.splice(i, 1);
+ }
+ }
+ }
+ return rules;
};
/******************************************************************************/
@@ -98,18 +164,16 @@ var renderRules = (function() {
details.hnSwitches.sort();
details.permanentRules.sort();
details.sessionRules.sort();
- var orig = details.hnSwitches.concat(details.permanentRules),
- edit = details.hnSwitches.concat(details.sessionRules);
- rulesToDoc(mergeView.leftOriginal(), orig);
- rulesToDoc(mergeView.editor(), edit);
- cleanEditText = mergeView.editor().getValue().trim();
+ unfilteredRules.orig.rules =
+ details.hnSwitches.concat(details.permanentRules);
+ unfilteredRules.edit.rules =
+ details.hnSwitches.concat(details.sessionRules);
+ rulesToDoc(firstVisit);
if ( firstVisit ) {
- mergeView.editor().clearHistory();
firstVisit = false;
mergeView.editor().execCommand('goNextDiff');
}
- cleanToken = mergeView.editor().changeGeneration();
- onChange(true);
+ onTextChanged(true);
};
})();
@@ -157,19 +221,14 @@ mergeView.options.revertChunk = function(
{ line: toStart.line, ch: 0 },
{ line: toEnd.line, ch: 0 }
);
- applyDiff(from === mv.editor(), toAdd, toRemove);
- to.replaceRange(toAdd, toStart, toEnd);
- cleanToken = mergeView.editor().changeGeneration();
- cleanEditText = mergeView.editor().getValue().trim();
+ applyDiff(from === mv.editor(), toAdd, toRemove, renderRules);
};
/******************************************************************************/
function handleImportFilePicker() {
var fileReaderOnLoadHandler = function() {
- if ( typeof this.result !== 'string' || this.result === '' ) {
- return;
- }
+ if ( typeof this.result !== 'string' || this.result === '' ) { return; }
// https://github.com/chrisaljoudi/uBlock/issues/757
// Support RequestPolicy rule syntax
var result = this.result;
@@ -217,41 +276,64 @@ function exportUserRulesToFile() {
/******************************************************************************/
-/*
-var onFilter = (function() {
- var timer;
+var onFilterChanged = (function() {
+ var timer,
+ overlay = null,
+ last = '';
var process = function() {
timer = undefined;
+ if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
+ if ( overlay !== null ) {
+ mergeView.leftOriginal().removeOverlay(overlay);
+ mergeView.editor().removeOverlay(overlay);
+ overlay = null;
+ }
+ var filter = uDom('#ruleFilter input').val();
+ if ( filter === last ) { return; }
+ last = filter;
+
+ if ( filter !== '' ) {
+ overlay = updateOverlay(filter);
+ mergeView.leftOriginal().addOverlay(overlay);
+ mergeView.editor().addOverlay(overlay);
+ }
+ rulesToDoc(true);
};
return function() {
if ( timer !== undefined ) { clearTimeout(timer); }
- timer = vAPI.setTimeout(process, 577);
+ timer = vAPI.setTimeout(process, 773);
};
})();
-*/
/******************************************************************************/
-var onChange = (function() {
+var onTextChanged = (function() {
var timer;
var process = function(now) {
timer = undefined;
- var isClean = mergeView.editor().isClean(cleanToken);
+ var isClean = mergeView.editor().isClean(cleanEditToken);
var diff = document.getElementById('diff');
if (
now &&
isClean === false &&
mergeView.editor().getValue().trim() === cleanEditText
) {
- cleanToken = mergeView.editor().changeGeneration();
+ cleanEditToken = mergeView.editor().changeGeneration();
isClean = true;
}
diff.classList.toggle('editing', isClean === false);
diff.classList.toggle('dirty', mergeView.leftChunks().length !== 0);
- CodeMirror.commands.save = isClean ? undefined : editSaveHandler;
+ var input = document.querySelector('#ruleFilter input');
+ if ( isClean ) {
+ input.removeAttribute('disabled');
+ CodeMirror.commands.save = undefined;
+ } else {
+ input.setAttribute('disabled', '');
+ CodeMirror.commands.save = editSaveHandler;
+ }
};
return function(now) {
@@ -308,7 +390,7 @@ var editSaveHandler = function() {
var editor = mergeView.editor();
var editText = editor.getValue().trim();
if ( editText === cleanEditText ) {
- onChange(true);
+ onTextChanged(true);
return;
}
if ( differ === undefined ) { differ = new diff_match_patch(); }
@@ -354,9 +436,10 @@ uDom('#exportButton').on('click', exportUserRulesToFile);
uDom('#revertButton').on('click', revertAllHandler);
uDom('#commitButton').on('click', commitAllHandler);
uDom('#editSaveButton').on('click', editSaveHandler);
+uDom('#ruleFilter input').on('input', onFilterChanged);
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
-mergeView.editor().on('updateDiff', function() { onChange(); });
+mergeView.editor().on('updateDiff', function() { onTextChanged(); });
/******************************************************************************/
diff --git a/src/lib/codemirror/addon/merge/merge.js b/src/lib/codemirror/addon/merge/merge.js
index 3a77f7a64..5b87b5e62 100644
--- a/src/lib/codemirror/addon/merge/merge.js
+++ b/src/lib/codemirror/addon/merge/merge.js
@@ -664,6 +664,7 @@
function getChunks(diff) {
var chunks = [];
+ if (!diff.length) return chunks;
var startEdit = 0, startOrig = 0;
var edit = Pos(0, 0), orig = Pos(0, 0);
for (var i = 0; i < diff.length; ++i) {