Skip to content

Commit

Permalink
Add CoreMirror's code-folding ability to list editor/viewer
Browse files Browse the repository at this point in the history
Related issue:
- uBlockOrigin/uBlock-issues#1134

CodeMirror's code folding reference:
- https://codemirror.net/doc/manual.html#addon_foldcode

This commit adds support for code-folding to the filter
list editor/viewer.

The following blocks of code are foldable by clicking the
corresponding marker in the gutter:

- !#if/#endif blocks
- !#include blocks

Addtionally, the following changes:

- The `!#include` line is now preserved when importing a
  sublist
- The `!#if` directives will be syntax-colored according
  to whether they evaluate to true or false on the current
  platform
- Double-clicking on a foldable line in the gutter will
  select the content of the foldable block
- Minor visual improvement to matching brackets
  • Loading branch information
gorhill committed Jul 10, 2020
1 parent f955d50 commit e44a568
Show file tree
Hide file tree
Showing 12 changed files with 443 additions and 28 deletions.
3 changes: 3 additions & 0 deletions src/1p-filters.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<title>uBlock — Your filters</title>

<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="lib/codemirror/addon/fold/foldgutter.css">
<link rel="stylesheet" href="lib/codemirror/addon/hint/show-hint.css">
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">

Expand Down Expand Up @@ -42,6 +43,8 @@
<script src="lib/codemirror/addon/display/panel.js"></script>
<script src="lib/codemirror/addon/edit/closebrackets.js"></script>
<script src="lib/codemirror/addon/edit/matchbrackets.js"></script>
<script src="lib/codemirror/addon/fold/foldcode.js"></script>
<script src="lib/codemirror/addon/fold/foldgutter.js"></script>
<script src="lib/codemirror/addon/hint/show-hint.js"></script>
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
Expand Down
3 changes: 3 additions & 0 deletions src/asset-viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title data-i18n="assetViewerPageName"></title>
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="lib/codemirror/addon/fold/foldgutter.css">
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
<link rel="stylesheet" type="text/css" href="css/themes/default.css">
<link rel="stylesheet" href="css/common.css">
Expand All @@ -31,6 +32,8 @@
<script src="lib/codemirror/lib/codemirror.js"></script>
<script src="lib/codemirror/addon/display/panel.js"></script>
<script src="lib/codemirror/addon/edit/matchbrackets.js"></script>
<script src="lib/codemirror/addon/fold/foldcode.js"></script>
<script src="lib/codemirror/addon/fold/foldgutter.js"></script>
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
Expand Down
1 change: 1 addition & 0 deletions src/css/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ div.CodeMirror span.CodeMirror-matchingbracket {
color: unset;
}
.CodeMirror-matchingbracket {
background-color: #afa;
color: inherit !important;
font-weight: bold;
}
Expand Down
2 changes: 2 additions & 0 deletions src/js/1p-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const cmEditor = new CodeMirror(document.getElementById('userFilters'), {
'Ctrl-Space': 'autocomplete',
'Tab': 'toggleComment',
},
foldGutter: true,
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
lineNumbers: true,
lineWrapping: true,
matchBrackets: true,
Expand Down
2 changes: 2 additions & 0 deletions src/js/asset-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@

const cmEditor = new CodeMirror(document.getElementById('content'), {
autofocus: true,
foldGutter: true,
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
lineNumbers: true,
lineWrapping: true,
matchBrackets: true,
Expand Down
2 changes: 1 addition & 1 deletion src/js/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ api.fetchFilterList = async function(mainlistURL) {
if ( sublistURLs.has(subURL) ) { continue; }
sublistURLs.add(subURL);
out.push(
slice.slice(lastIndex, match.index),
slice.slice(lastIndex, match.index + match[0].length),
`! >>>>>>>> ${subURL}`,
api.fetchText(subURL),
`! <<<<<<<< ${subURL}`
Expand Down
92 changes: 80 additions & 12 deletions src/js/codemirror/ubo-static-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

const redirectNames = new Map();
const scriptletNames = new Map();
const preparseDirectiveTokens = new Set();
const preparseDirectiveTokens = new Map();
const preparseDirectiveHints = [];

/******************************************************************************/
Expand All @@ -44,8 +44,8 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
if ( StaticFilteringParser instanceof Object === false ) { return; }
const parser = new StaticFilteringParser({ interactive: true });

const rePreparseDirectives = /^!#(?:if|endif|include)\b/;
const rePreparseIfDirective = /^(!#if !?)(.+)$/;
const rePreparseDirectives = /^!#(?:if|endif|include )\b/;
const rePreparseIfDirective = /^(!#if ?)(.*)$/;
let parserSlot = 0;
let netOptionValueMode = false;

Expand All @@ -60,17 +60,28 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
return 'variable strong';
}
if ( stream.pos < match[1].length ) {
stream.pos = match[1].length;
stream.pos += match[1].length;
return 'variable strong';
}
stream.skipToEnd();
if (
preparseDirectiveTokens.size === 0 ||
preparseDirectiveTokens.has(match[2].trim())
) {
return 'variable strong';
if ( match[1].endsWith(' ') === false ) {
return 'error strong';
}
if ( preparseDirectiveTokens.size === 0 ) {
return 'positive strong';
}
let token = match[2];
const not = token.startsWith('!');
if ( not ) {
token = token.slice(1);
}
if ( preparseDirectiveTokens.has(token) === false ) {
return 'error strong';
}
return 'error strong';
if ( not !== preparseDirectiveTokens.get(token) ) {
return 'positive strong';
}
return 'negative strong';
};

const colorExtHTMLPatternSpan = function(stream) {
Expand Down Expand Up @@ -289,8 +300,8 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
scriptletNames.set(name.slice(0, -3), displayText);
}
}
details.preparseDirectiveTokens.forEach(a => {
preparseDirectiveTokens.add(a);
details.preparseDirectiveTokens.forEach(([ a, b ]) => {
preparseDirectiveTokens.set(a, b);
});
preparseDirectiveHints.push(...details.preparseDirectiveHints);
initHints();
Expand Down Expand Up @@ -471,6 +482,63 @@ const initHints = function() {

/******************************************************************************/

CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {

const foldIfEndif = function(startLineNo, startLine, cm) {
const lastLineNo = cm.lastLine();
let endLineNo = startLineNo;
let depth = 1;
while ( endLineNo < lastLineNo ) {
endLineNo += 1;
const line = cm.getLine(endLineNo);
if ( line.startsWith('!#endif') ) {
depth -= 1;
if ( depth === 0 ) {
return {
from: CodeMirror.Pos(startLineNo, startLine.length),
to: CodeMirror.Pos(endLineNo, 0)
};
}
}
if ( line.startsWith('!#if') ) {
depth += 1;
}
}
};

const foldInclude = function(startLineNo, startLine, cm) {
const lastLineNo = cm.lastLine();
let endLineNo = startLineNo + 1;
if ( endLineNo >= lastLineNo ) { return; }
if ( cm.getLine(endLineNo).startsWith('! >>>>>>>> ') === false ) {
return;
}
while ( endLineNo < lastLineNo ) {
endLineNo += 1;
const line = cm.getLine(endLineNo);
if ( line.startsWith('! <<<<<<<< ') ) {
return {
from: CodeMirror.Pos(startLineNo, startLine.length),
to: CodeMirror.Pos(endLineNo, line.length)
};
}
}
};

return function(cm, start) {
const startLineNo = start.line;
const startLine = cm.getLine(startLineNo);
if ( startLine.startsWith('!#if') ) {
return foldIfEndif(startLineNo, startLine, cm);
}
if ( startLine.startsWith('!#include ') ) {
return foldInclude(startLineNo, startLine, cm);
}
};
})());

/******************************************************************************/

// <<<<< end of local scope
}

Expand Down
33 changes: 23 additions & 10 deletions src/js/dashboard-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,24 +149,37 @@ self.uBlockDashboard.patchCodeMirrorEditor = (function() {
let lastGutterClick = 0;
let lastGutterLine = 0;

const onGutterClicked = function(cm, line) {
const onGutterClicked = function(cm, line, gutter) {
if ( gutter !== 'CodeMirror-linenumbers' ) { return; }
grabFocusAsync(cm);
const delta = Date.now() - lastGutterClick;
// Single click
if ( delta >= 500 || line !== lastGutterLine ) {
cm.setSelection(
{ line: line, ch: 0 },
{ line, ch: 0 },
{ line: line + 1, ch: 0 }
);
lastGutterClick = Date.now();
lastGutterLine = line;
} else {
cm.setSelection(
{ line: 0, ch: 0 },
{ line: cm.lineCount(), ch: 0 },
{ scroll: false }
);
lastGutterClick = 0;
return;
}
grabFocusAsync(cm);
// Double click: select fold-able block or all
let lineFrom = 0;
let lineTo = cm.lineCount();
const foldFn = cm.getHelper({ line, ch: 0 }, 'fold');
if ( foldFn instanceof Function ) {
const range = foldFn(cm, { line, ch: 0 });
if ( range !== undefined ) {
lineFrom = range.from.line;
lineTo = range.to.line + 1;
}
}
cm.setSelection(
{ line: lineFrom, ch: 0 },
{ line: lineTo, ch: 0 },
{ scroll: false }
);
lastGutterClick = 0;
};

return function(cm) {
Expand Down
15 changes: 10 additions & 5 deletions src/js/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
// the content string which should alternatively be parsed and discarded.
split: function(content) {
const reIf = /^!#(if|endif)\b([^\n]*)(?:[\n\r]+|$)/gm;
const soup = vAPI.webextFlavor.soup;
const stack = [];
const shouldDiscard = ( ) => stack.some(v => v);
const parts = [ 0 ];
Expand All @@ -878,10 +879,8 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
if ( target ) { expr = expr.slice(1); }
const token = this.tokens.get(expr);
const startDiscard =
token === 'false' &&
target === false ||
token !== undefined &&
vAPI.webextFlavor.soup.has(token) === target;
token === 'false' && target === false ||
token !== undefined && soup.has(token) === target;
if ( discard === false && startDiscard ) {
parts.push(match.index);
discard = true;
Expand Down Expand Up @@ -930,7 +929,12 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
},

getTokens: function() {
return Array.from(this.tokens.keys());
const out = new Map();
const soup = vAPI.webextFlavor.soup;
for ( const [ key, val ] of this.tokens ) {
out.set(key, val !== 'false' && soup.has(val));
}
return Array.from(out);
},

tokens: new Map([
Expand All @@ -947,6 +951,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
// Compatibility with other blockers
// https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#adguard-specific
[ 'adguard', 'adguard' ],
[ 'adguard_app_windows', 'false' ],
[ 'adguard_ext_chromium', 'chromium' ],
[ 'adguard_ext_edge', 'edge' ],
[ 'adguard_ext_firefox', 'firefox' ],
Expand Down
Loading

0 comments on commit e44a568

Please sign in to comment.