From aa000e282ee501d35735008b0b7bf205000d66ca Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 16 Jun 2020 08:59:55 -0400 Subject: [PATCH] Add auto-completion for procedural operators Related commit: - https://github.com/gorhill/uBlock/commit/3e72a47c1fa52af3163f370facf5b9046a7b10b0 --- src/css/codemirror.css | 4 ++ src/js/codemirror/ubo-static-filtering.js | 49 +++++++++++++++++------ src/js/static-filtering-parser.js | 44 ++++++++++---------- 3 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/css/codemirror.css b/src/css/codemirror.css index 835a093c4..337ce9a7d 100644 --- a/src/css/codemirror.css +++ b/src/css/codemirror.css @@ -127,3 +127,7 @@ div.CodeMirror span.CodeMirror-matchingbracket { .CodeMirror-merge-gap { vertical-align: top; } + +.CodeMirror-hints { + z-index: 10000; + } diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js index 5d5f35920..d8a4b21cc 100644 --- a/src/js/codemirror/ubo-static-filtering.js +++ b/src/js/codemirror/ubo-static-filtering.js @@ -262,8 +262,13 @@ CodeMirror.defineMode('ubo-static-filtering', function() { const parser = new StaticFilteringParser(); const redirectNames = new Map(); const scriptletNames = new Map(); + const proceduralOperatorNames = new Map( + Array.from(parser.proceduralOperatorTokens).filter(item => { + return (item[1] & 0b01) !== 0; + }) + ); - const getNetOptionHint = function(cursor, isNegated, seedLeft, seedRight) { + const getNetOptionHints = function(cursor, isNegated, seedLeft, seedRight) { const assignPos = seedRight.indexOf('='); if ( assignPos !== -1 ) { seedRight = seedRight.slice(0, assignPos); } const seed = (seedLeft + seedRight).trim(); @@ -289,7 +294,7 @@ CodeMirror.defineMode('ubo-static-filtering', function() { }; }; - const getNetRedirectHint = function(cursor, seedLeft, seedRight) { + const getNetRedirectHints = function(cursor, seedLeft, seedRight) { const seed = (seedLeft + seedRight).trim(); const out = []; for ( let text of redirectNames.keys() ) { @@ -303,7 +308,7 @@ CodeMirror.defineMode('ubo-static-filtering', function() { }; }; - const getNetHint = function(cursor, line) { + const getNetHints = function(cursor, line) { const beg = cursor.ch; if ( beg < parser.optionsSpan ) { return; } const lineBefore = line.slice(0, beg); @@ -313,21 +318,41 @@ CodeMirror.defineMode('ubo-static-filtering', function() { if ( matchLeft === null || matchRight === null ) { return; } let pos = matchLeft[1].indexOf('='); if ( pos === -1 ) { - return getNetOptionHint( + return getNetOptionHints( cursor, matchLeft[0].startsWith('~'), matchLeft[1], matchRight[1] ); } - return getNetRedirectHint( + return getNetRedirectHints( cursor, matchLeft[1].slice(pos + 1), matchRight[1] ); }; - const getExtScriptletHint = function(cursor, line) { + const getExtSelectorHints = function(cursor, line) { + const beg = cursor.ch; + const matchLeft = /#\^?.*:([^:]*)$/.exec(line.slice(0, beg)); + const matchRight = /^([a-z-]*)\(?/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + const isStaticDOM = matchLeft[0].indexOf('^') !== -1; + const seed = (matchLeft[1] + matchRight[1]).trim(); + const out = []; + for ( let [ text, bits ] of proceduralOperatorNames ) { + if ( text.startsWith(seed) === false ) { continue; } + if ( isStaticDOM && (bits & 0b10) !== 0 ) { continue; } + out.push(text); + } + return { + from: { line: cursor.line, ch: cursor.ch - matchLeft[1].length }, + to: { line: cursor.line, ch: cursor.ch + matchRight[1].length }, + list: out, + }; + }; + + const getExtScriptletHints = function(cursor, line) { const beg = cursor.ch; const matchLeft = /#\+\js\(([^,]*)$/.exec(line.slice(0, beg)); const matchRight = /^([^,)]*)/.exec(line.slice(beg)); @@ -353,14 +378,14 @@ CodeMirror.defineMode('ubo-static-filtering', function() { const cursor = cm.getCursor(); const line = cm.getLine(cursor.line); parser.analyze(line); - if ( - parser.category === parser.CATStaticExtFilter && - parser.hasFlavor(parser.BITFlavorExtScriptlet) - ) { - return getExtScriptletHint(cursor, line); + if ( parser.category === parser.CATStaticExtFilter ) { + if ( parser.hasFlavor(parser.BITFlavorExtScriptlet) ) { + return getExtScriptletHints(cursor, line); + } + return getExtSelectorHints(cursor, line); } if ( parser.category === parser.CATStaticNetFilter ) { - return getNetHint(cursor, line); + return getNetHints(cursor, line); } }; diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 25aa2b19f..df77a3965 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -1101,27 +1101,7 @@ Parser.prototype.SelectorCompiler = class { this.rePseudoClass = /:(?::?after|:?before|:[a-z][a-z-]*[a-z])$/; this.reProceduralOperator = new RegExp([ '^(?:', - [ - '-abp-contains', - '-abp-has', - 'contains', - 'has', - 'has-text', - 'if', - 'if-not', - 'matches-css', - 'matches-css-after', - 'matches-css-before', - 'min-text-length', - 'not', - 'nth-ancestor', - 'remove', - 'style', - 'upward', - 'watch-attr', - 'watch-attrs', - 'xpath' - ].join('|'), + Array.from(parser.proceduralOperatorTokens.keys()).join('|'), ')\\(' ].join('')); this.reEatBackslashes = /\\([()])/g; @@ -1627,6 +1607,28 @@ Parser.prototype.SelectorCompiler = class { } }; +Parser.prototype.proceduralOperatorTokens = new Map([ + [ '-abp-contains', 0b00 ], + [ '-abp-has', 0b00, ], + [ 'contains', 0b00, ], + [ 'has', 0b01 ], + [ 'has-text', 0b01 ], + [ 'if', 0b00 ], + [ 'if-not', 0b00 ], + [ 'matches-css', 0b11 ], + [ 'matches-css-after', 0b11 ], + [ 'matches-css-before', 0b11 ], + [ 'min-text-length', 0b01 ], + [ 'not', 0b01 ], + [ 'nth-ancestor', 0b00 ], + [ 'remove', 0b11 ], + [ 'style', 0b11 ], + [ 'upward', 0b01 ], + [ 'watch-attr', 0b11 ], + [ 'watch-attrs', 0b00 ], + [ 'xpath', 0b01 ], +]); + /******************************************************************************/ const hasNoBits = (v, bits) => (v & bits) === 0;