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) {