mirror of https://github.com/gorhill/uBlock.git
Add auto-completion capability for filter options
Related commit:
- 3e72a47c1f
Use ctrl-space to auto-complete filter options and
`redirect=` resources in _"My filters"_ pane.
This commit is contained in:
parent
5cb2283736
commit
c9cfd62c21
|
@ -26,7 +26,12 @@
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
CodeMirror.defineMode('ubo-static-filtering', function() {
|
CodeMirror.defineMode('ubo-static-filtering', function() {
|
||||||
const parser = new vAPI.StaticFilteringParser({ interactive: true });
|
const StaticFilteringParser = typeof vAPI === 'object'
|
||||||
|
? vAPI.StaticFilteringParser
|
||||||
|
: self.StaticFilteringParser;
|
||||||
|
if ( StaticFilteringParser instanceof Object === false ) { return; }
|
||||||
|
const parser = new StaticFilteringParser({ interactive: true });
|
||||||
|
|
||||||
const reDirective = /^!#(?:if|endif|include)\b/;
|
const reDirective = /^!#(?:if|endif|include)\b/;
|
||||||
let parserSlot = 0;
|
let parserSlot = 0;
|
||||||
let netOptionValueMode = false;
|
let netOptionValueMode = false;
|
||||||
|
@ -245,46 +250,95 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
|
||||||
|
|
||||||
// Following code is for auto-completion. Reference:
|
// Following code is for auto-completion. Reference:
|
||||||
// https://codemirror.net/demo/complete.html
|
// https://codemirror.net/demo/complete.html
|
||||||
//
|
|
||||||
// TODO: implement auto-completion for `redirect=`
|
|
||||||
|
|
||||||
(( ) => {
|
(( ) => {
|
||||||
if ( typeof vAPI !== 'object' ) { return; }
|
if ( typeof vAPI !== 'object' ) { return; }
|
||||||
|
|
||||||
let resourceNames = new Map();
|
const StaticFilteringParser = typeof vAPI === 'object'
|
||||||
|
? vAPI.StaticFilteringParser
|
||||||
|
: self.StaticFilteringParser;
|
||||||
|
if ( StaticFilteringParser instanceof Object === false ) { return; }
|
||||||
|
|
||||||
vAPI.messaging.send('dashboard', {
|
const parser = new StaticFilteringParser();
|
||||||
what: 'getResourceDetails'
|
const redirectNames = new Map();
|
||||||
}).then(response => {
|
const scriptletNames = new Map();
|
||||||
if ( Array.isArray(response) === false ) { return; }
|
|
||||||
resourceNames = new Map(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
const parser = new vAPI.StaticFilteringParser();
|
const getNetOptionHint = function(cursor, isNegated, seedLeft, seedRight) {
|
||||||
|
const assignPos = seedRight.indexOf('=');
|
||||||
const getHints = function(cm) {
|
if ( assignPos !== -1 ) { seedRight = seedRight.slice(0, assignPos); }
|
||||||
const cursor = cm.getCursor();
|
const seed = (seedLeft + seedRight).trim();
|
||||||
const line = cm.getLine(cursor.line);
|
const isException = parser.isException();
|
||||||
parser.analyze(line);
|
const out = [];
|
||||||
if ( parser.category !== parser.CATStaticExtFilter ) {
|
for ( let [ name, bits ] of parser.netOptionTokens ) {
|
||||||
return;
|
if ( name.startsWith(seed) === false ) { continue; }
|
||||||
|
if ( isNegated && (bits & parser.OPTCanNegate) === 0 ) { continue; }
|
||||||
|
if ( isException ) {
|
||||||
|
if ( (bits & parser.OPTBlockOnly) !== 0 ) { continue; }
|
||||||
|
} else {
|
||||||
|
if ( (bits & parser.OPTAllowOnly) !== 0 ) { continue; }
|
||||||
|
if ( (assignPos === -1) && (bits & parser.OPTMustAssign) !== 0 ) {
|
||||||
|
name += '=';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push(name);
|
||||||
}
|
}
|
||||||
if ( parser.hasFlavor(parser.BITFlavorExtScriptlet) === false ) {
|
return {
|
||||||
return;
|
from: { line: cursor.line, ch: cursor.ch - seedLeft.length },
|
||||||
|
to: { line: cursor.line, ch: cursor.ch + seedRight.length },
|
||||||
|
list: out,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNetRedirectHint = function(cursor, seedLeft, seedRight) {
|
||||||
|
const seed = (seedLeft + seedRight).trim();
|
||||||
|
const out = [];
|
||||||
|
for ( let text of redirectNames.keys() ) {
|
||||||
|
if ( text.startsWith(seed) === false ) { continue; }
|
||||||
|
out.push(text);
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
from: { line: cursor.line, ch: cursor.ch - seedLeft.length },
|
||||||
|
to: { line: cursor.line, ch: cursor.ch + seedRight.length },
|
||||||
|
list: out,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNetHint = function(cursor, line) {
|
||||||
|
const beg = cursor.ch;
|
||||||
|
if ( beg < parser.optionsSpan ) { return; }
|
||||||
|
const lineBefore = line.slice(0, beg);
|
||||||
|
const lineAfter = line.slice(beg);
|
||||||
|
let matchLeft = /~?([^$,~]*)$/.exec(lineBefore);
|
||||||
|
let matchRight = /^([^,]*)/.exec(lineAfter);
|
||||||
|
if ( matchLeft === null || matchRight === null ) { return; }
|
||||||
|
let pos = matchLeft[1].indexOf('=');
|
||||||
|
if ( pos === -1 ) {
|
||||||
|
return getNetOptionHint(
|
||||||
|
cursor,
|
||||||
|
matchLeft[0].startsWith('~'),
|
||||||
|
matchLeft[1],
|
||||||
|
matchRight[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return getNetRedirectHint(
|
||||||
|
cursor,
|
||||||
|
matchLeft[1].slice(pos + 1),
|
||||||
|
matchRight[1]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExtScriptletHint = function(cursor, line) {
|
||||||
const beg = cursor.ch;
|
const beg = cursor.ch;
|
||||||
const matchLeft = /#\+\js\(([^,]*)$/.exec(line.slice(0, beg));
|
const matchLeft = /#\+\js\(([^,]*)$/.exec(line.slice(0, beg));
|
||||||
const matchRight = /^([^,)]*)/.exec(line.slice(beg));
|
const matchRight = /^([^,)]*)/.exec(line.slice(beg));
|
||||||
if ( matchLeft === null || matchRight === null ) { return; }
|
if ( matchLeft === null || matchRight === null ) { return; }
|
||||||
const seed = (matchLeft[1] + matchRight[1]).trim();
|
const seed = (matchLeft[1] + matchRight[1]).trim();
|
||||||
const out = [];
|
const out = [];
|
||||||
for ( const [ name, details ] of resourceNames ) {
|
for ( const [ text, displayText ] of scriptletNames ) {
|
||||||
if ( name.startsWith(seed) === false ) { continue; }
|
if ( text.startsWith(seed) === false ) { continue; }
|
||||||
if ( details.hasData !== true ) { continue; }
|
const hint = { text };
|
||||||
if ( name.endsWith('.js') === false ) { continue; }
|
if ( displayText !== '' ) {
|
||||||
const hint = { text: name.slice(0, -3) };
|
hint.displayText = displayText;
|
||||||
if ( details.aliasOf !== '' ) {
|
|
||||||
hint.displayText = `${hint.text} (${details.aliasOf})`;
|
|
||||||
}
|
}
|
||||||
out.push(hint);
|
out.push(hint);
|
||||||
}
|
}
|
||||||
|
@ -295,7 +349,38 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
CodeMirror.registerHelper('hint', 'ubo-static-filtering', getHints);
|
const getHints = function(cm) {
|
||||||
|
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.CATStaticNetFilter ) {
|
||||||
|
return getNetHint(cursor, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'getResourceDetails'
|
||||||
|
}).then(response => {
|
||||||
|
if ( Array.isArray(response) === false ) { return; }
|
||||||
|
for ( const [ name, details ] of response ) {
|
||||||
|
const displayText = details.aliasOf !== ''
|
||||||
|
? `${name} (${details.aliasOf})`
|
||||||
|
: '';
|
||||||
|
if ( details.canRedirect ) {
|
||||||
|
redirectNames.set(name, displayText);
|
||||||
|
}
|
||||||
|
if ( details.canInject && name.endsWith('.js') ) {
|
||||||
|
scriptletNames.set(name.slice(0, -3), displayText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CodeMirror.registerHelper('hint', 'ubo-static-filtering', getHints);
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -784,7 +784,11 @@ RedirectEngine.prototype.loadBuiltinResources = function() {
|
||||||
RedirectEngine.prototype.getResourceDetails = function() {
|
RedirectEngine.prototype.getResourceDetails = function() {
|
||||||
const out = new Map();
|
const out = new Map();
|
||||||
for ( const [ name, entry ] of this.resources ) {
|
for ( const [ name, entry ] of this.resources ) {
|
||||||
out.set(name, { hasData: entry.data !== '', aliasOf: '' });
|
out.set(name, {
|
||||||
|
canInject: typeof entry.data === 'string',
|
||||||
|
canRedirect: entry.warURL !== undefined,
|
||||||
|
aliasOf: '',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
for ( const [ alias, name ] of this.aliases ) {
|
for ( const [ alias, name ] of this.aliases ) {
|
||||||
const original = out.get(name);
|
const original = out.get(name);
|
||||||
|
|
|
@ -1933,6 +1933,69 @@ Parser.prototype.OPTTokenXhr = OPTTokenXhr;
|
||||||
Parser.prototype.OPTTokenWebrtc = OPTTokenWebrtc;
|
Parser.prototype.OPTTokenWebrtc = OPTTokenWebrtc;
|
||||||
Parser.prototype.OPTTokenWebsocket = OPTTokenWebsocket;
|
Parser.prototype.OPTTokenWebsocket = OPTTokenWebsocket;
|
||||||
|
|
||||||
|
Parser.prototype.OPTCanNegate = OPTCanNegate;
|
||||||
|
Parser.prototype.OPTBlockOnly = OPTBlockOnly;
|
||||||
|
Parser.prototype.OPTAllowOnly = OPTAllowOnly;
|
||||||
|
Parser.prototype.OPTMustAssign = OPTMustAssign;
|
||||||
|
Parser.prototype.OPTAllowMayAssign = OPTAllowMayAssign;
|
||||||
|
Parser.prototype.OPTDomainList = OPTDomainList;
|
||||||
|
Parser.prototype.OPTType = OPTType;
|
||||||
|
Parser.prototype.OPTNetworkType = OPTNetworkType;
|
||||||
|
Parser.prototype.OPTRedirectType = OPTRedirectType;
|
||||||
|
Parser.prototype.OPTNotSupported = OPTNotSupported;
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const netOptionTokens = new Map([
|
||||||
|
[ '1p', OPTToken1p | OPTCanNegate ],
|
||||||
|
[ 'first-party', OPTToken1p | OPTCanNegate ],
|
||||||
|
[ '3p', OPTToken3p | OPTCanNegate ],
|
||||||
|
[ 'third-party', OPTToken3p | OPTCanNegate ],
|
||||||
|
[ 'all', OPTTokenAll | OPTType | OPTNetworkType ],
|
||||||
|
[ 'badfilter', OPTTokenBadfilter ],
|
||||||
|
[ 'cname', OPTTokenCname | OPTAllowOnly | OPTType ],
|
||||||
|
[ 'csp', OPTTokenCsp | OPTMustAssign | OPTAllowMayAssign ],
|
||||||
|
[ 'css', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'stylesheet', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'denyallow', OPTTokenDenyAllow | OPTMustAssign | OPTDomainList ],
|
||||||
|
[ 'doc', OPTTokenDoc | OPTType | OPTNetworkType ],
|
||||||
|
[ 'document', OPTTokenDoc | OPTType | OPTNetworkType ],
|
||||||
|
[ 'domain', OPTTokenDomain | OPTMustAssign | OPTDomainList ],
|
||||||
|
[ 'ehide', OPTTokenEhide | OPTType ],
|
||||||
|
[ 'elemhide', OPTTokenEhide | OPTType ],
|
||||||
|
[ 'empty', OPTTokenEmpty | OPTBlockOnly | OPTType | OPTNetworkType | OPTBlockOnly | OPTRedirectType ],
|
||||||
|
[ 'frame', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'subdocument', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'font', OPTTokenFont | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'genericblock', OPTTokenGenericblock | OPTNotSupported ],
|
||||||
|
[ 'ghide', OPTTokenGhide | OPTType ],
|
||||||
|
[ 'generichide', OPTTokenGhide | OPTType ],
|
||||||
|
[ 'image', OPTTokenImage | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'important', OPTTokenImportant | OPTBlockOnly ],
|
||||||
|
[ 'inline-font', OPTTokenInlineFont | OPTType ],
|
||||||
|
[ 'inline-script', OPTTokenInlineScript | OPTType ],
|
||||||
|
[ 'media', OPTTokenMedia | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'mp4', OPTTokenMp4 | OPTType | OPTNetworkType | OPTBlockOnly | OPTRedirectType ],
|
||||||
|
[ 'object', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'object-subrequest', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'other', OPTTokenOther | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'ping', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'beacon', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'popunder', OPTTokenPopunder | OPTType ],
|
||||||
|
[ 'popup', OPTTokenPopup | OPTType ],
|
||||||
|
[ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTBlockOnly | OPTRedirectType ],
|
||||||
|
[ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTBlockOnly | OPTRedirectType ],
|
||||||
|
[ 'script', OPTTokenScript | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'shide', OPTTokenShide | OPTType ],
|
||||||
|
[ 'specifichide', OPTTokenShide | OPTType ],
|
||||||
|
[ 'xhr', OPTTokenXhr | OPTCanNegate| OPTType | OPTNetworkType ],
|
||||||
|
[ 'xmlhttprequest', OPTTokenXhr | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
[ 'webrtc', OPTTokenWebrtc | OPTNotSupported ],
|
||||||
|
[ 'websocket', OPTTokenWebsocket | OPTCanNegate | OPTType | OPTNetworkType ],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Parser.prototype.netOptionTokens = netOptionTokens;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const Span = class {
|
const Span = class {
|
||||||
|
@ -2160,54 +2223,6 @@ const NetOptionsIterator = class {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const netOptionTokens = new Map([
|
|
||||||
[ '1p', OPTToken1p | OPTCanNegate ],
|
|
||||||
[ 'first-party', OPTToken1p | OPTCanNegate ],
|
|
||||||
[ '3p', OPTToken3p | OPTCanNegate ],
|
|
||||||
[ 'third-party', OPTToken3p | OPTCanNegate ],
|
|
||||||
[ 'all', OPTTokenAll | OPTType | OPTNetworkType ],
|
|
||||||
[ 'badfilter', OPTTokenBadfilter ],
|
|
||||||
[ 'cname', OPTTokenCname | OPTAllowOnly | OPTType ],
|
|
||||||
[ 'csp', OPTTokenCsp | OPTMustAssign | OPTAllowMayAssign ],
|
|
||||||
[ 'css', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'stylesheet', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'denyallow', OPTTokenDenyAllow | OPTMustAssign | OPTDomainList ],
|
|
||||||
[ 'doc', OPTTokenDoc | OPTType | OPTNetworkType ],
|
|
||||||
[ 'document', OPTTokenDoc | OPTType | OPTNetworkType ],
|
|
||||||
[ 'domain', OPTTokenDomain | OPTMustAssign | OPTDomainList ],
|
|
||||||
[ 'ehide', OPTTokenEhide | OPTType ],
|
|
||||||
[ 'elemhide', OPTTokenEhide | OPTType ],
|
|
||||||
[ 'empty', OPTTokenEmpty | OPTBlockOnly | OPTType | OPTNetworkType | OPTBlockOnly | OPTRedirectType ],
|
|
||||||
[ 'frame', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'subdocument', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'font', OPTTokenFont | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'genericblock', OPTTokenGenericblock | OPTNotSupported ],
|
|
||||||
[ 'ghide', OPTTokenGhide | OPTType ],
|
|
||||||
[ 'generichide', OPTTokenGhide | OPTType ],
|
|
||||||
[ 'image', OPTTokenImage | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'important', OPTTokenImportant | OPTBlockOnly ],
|
|
||||||
[ 'inline-font', OPTTokenInlineFont | OPTType ],
|
|
||||||
[ 'inline-script', OPTTokenInlineScript | OPTType ],
|
|
||||||
[ 'media', OPTTokenMedia | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'mp4', OPTTokenMp4 | OPTType | OPTNetworkType | OPTBlockOnly | OPTRedirectType ],
|
|
||||||
[ 'object', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'object-subrequest', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'other', OPTTokenOther | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'ping', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'beacon', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'popunder', OPTTokenPopunder | OPTType ],
|
|
||||||
[ 'popup', OPTTokenPopup | OPTType ],
|
|
||||||
[ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTBlockOnly | OPTRedirectType ],
|
|
||||||
[ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTBlockOnly | OPTRedirectType ],
|
|
||||||
[ 'script', OPTTokenScript | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'shide', OPTTokenShide | OPTType ],
|
|
||||||
[ 'specifichide', OPTTokenShide | OPTType ],
|
|
||||||
[ 'xhr', OPTTokenXhr | OPTCanNegate| OPTType | OPTNetworkType ],
|
|
||||||
[ 'xmlhttprequest', OPTTokenXhr | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
[ 'webrtc', OPTTokenWebrtc | OPTNotSupported ],
|
|
||||||
[ 'websocket', OPTTokenWebsocket | OPTCanNegate | OPTType | OPTNetworkType ],
|
|
||||||
]);
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/997
|
// https://github.com/gorhill/uBlock/issues/997
|
||||||
|
|
Loading…
Reference in New Issue