diff --git a/src/dyna-rules.html b/src/dyna-rules.html
index 87bc10a0c..12353f354 100644
--- a/src/dyna-rules.html
+++ b/src/dyna-rules.html
@@ -48,6 +48,8 @@
+
+
diff --git a/src/js/codemirror/ubo-dynamic-filtering.js b/src/js/codemirror/ubo-dynamic-filtering.js
new file mode 100644
index 000000000..cc493e484
--- /dev/null
+++ b/src/js/codemirror/ubo-dynamic-filtering.js
@@ -0,0 +1,187 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2019-present 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-dynamic-filtering', ( ) => {
+
+ const validSwitches = new Set([
+ 'no-strict-blocking:',
+ 'no-popups:',
+ 'no-cosmetic-filtering:',
+ 'no-remote-fonts:',
+ 'no-large-media:',
+ 'no-csp-reports:',
+ 'no-scripting:',
+ ]);
+ const validSwitcheStates = new Set([
+ 'true',
+ 'false',
+ ]);
+ const validTypes = new Set([
+ '*',
+ '3p',
+ 'image',
+ 'inline-script',
+ '1p-script',
+ '3p-script',
+ '3p-frame',
+ ]);
+ const validActions = new Set([
+ 'block',
+ 'allow',
+ 'noop',
+ ]);
+ const reIsNotHostname = /[:/#?*]/;
+ const tokens = [];
+
+ const isSwitchRule = ( ) => {
+ const token = tokens[0];
+ return token.charCodeAt(token.length-1) === 0x3A /* ':' */;
+ };
+
+ const isURLRule = ( ) => {
+ return /^[\w-_]+:\/\/[^/]+\//.test(tokens[1]);
+ };
+
+ const skipToEnd = (stream, style = null) => {
+ stream.skipToEnd();
+ return style;
+ };
+
+ const token = stream => {
+ if ( stream.sol() ) { tokens.length = 0; }
+ stream.eatSpace();
+ const match = stream.match(/\S+/);
+ if ( Array.isArray(match) === false ) {
+ return skipToEnd(stream);
+ }
+ if ( tokens.length === 4 ) {
+ return skipToEnd(stream, 'error');
+ }
+ const token = match[0];
+ tokens.push(token);
+ // Field 1: per-site switch or hostname
+ if ( tokens.length === 1 ) {
+ if ( isSwitchRule(token) ) {
+ if ( validSwitches.has(token) === false ) {
+ return skipToEnd(stream, 'error');
+ }
+ } else if ( reIsNotHostname.test(token) && token !== '*' ) {
+ return skipToEnd(stream, 'error');
+ }
+ return null;
+ }
+ // Field 2: hostname or url
+ if ( tokens.length === 2 ) {
+ if ( isSwitchRule(tokens[0]) ) {
+ if ( reIsNotHostname.test(token) && token !== '*' ) {
+ return skipToEnd(stream, 'error');
+ }
+ }
+ if (
+ reIsNotHostname.test(token) &&
+ token !== '*' &&
+ isURLRule(token) === false
+ ) {
+ return skipToEnd(stream, 'error');
+ }
+ return null;
+ }
+ // Field 3
+ if ( tokens.length === 3 ) {
+ if ( isSwitchRule(tokens[0]) ) {
+ if ( validSwitcheStates.has(token) === false ) {
+ return skipToEnd(stream, 'error');
+ }
+ return null;
+ }
+ if ( isURLRule(tokens[1]) === false ) {
+ if (
+ tokens[1] !== '*' && token !== '*' ||
+ tokens[1] === '*' && validTypes.has(token) === false
+ ) {
+ return skipToEnd(stream, 'error');
+ }
+ }
+ return null;
+ }
+ // Field 4
+ if ( tokens.length === 4 ) {
+ if (
+ isSwitchRule(tokens[0]) ||
+ validActions.has(token) === false
+ ) {
+ return skipToEnd(stream, 'error');
+ }
+ return null;
+ }
+ return skipToEnd(stream);
+ };
+
+ return { token };
+});
+
+/*
+Code below is to address
+https://github.com/uBlockOrigin/uMatrix-issues/issues/128
+
+But this needs fixing because glitchiness in some cases.
+I may end up having to create a custom merge view rather
+than using the existing CodeMirror one.
+
+CodeMirror.registerHelper('fold', 'ubo-dynamic-filtering', (cm, start) => {
+ function isHeader(lineNo) {
+ const tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));
+ return tokentype && /\bheader\b/.test(tokentype);
+ }
+
+ function headerLevel(lineNo, line, nextLine) {
+ let match = line && line.match(/^#+/);
+ if (match && isHeader(lineNo)) return match[0].length;
+ match = nextLine && nextLine.match(/^[=\-]+\s*$/);
+ if (match && isHeader(lineNo + 1)) return nextLine[0] === '=' ? 1 : 2;
+ return 100;
+ }
+
+ const firstLine = cm.getLine(start.line);
+ let nextLine = cm.getLine(start.line + 1);
+ const level = headerLevel(start.line, firstLine, nextLine);
+ if ( level === 100 ) { return; }
+
+ const lastLineNo = cm.lastLine();
+ let end = start.line,
+ nextNextLine = cm.getLine(end + 2);
+ while ( end < lastLineNo ) {
+ if ( headerLevel(end + 1, nextLine, nextNextLine) <= level ) { break; }
+ ++end;
+ nextLine = nextNextLine;
+ nextNextLine = cm.getLine(end + 2);
+ }
+
+ return {
+ from: CodeMirror.Pos(start.line, firstLine.length),
+ to: CodeMirror.Pos(end, cm.getLine(end).length),
+ };
+});
+*/