mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
Use custom linter gutter as container for code-folding widgets
This allows to reduce the horizontal size of the gutter; more efficient management of folding since we already need to parse each line for the linter; and eventually this will also allows to detect unbalanced !#if-!#endif directives and report such cases as errors. Additionally, keep incrementally improving error reporting details in the linter.
This commit is contained in:
parent
028eee456f
commit
bc21a1fe72
10 changed files with 343 additions and 106 deletions
|
@ -6,7 +6,6 @@
|
|||
<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">
|
||||
|
||||
|
@ -44,7 +43,6 @@
|
|||
<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/searchcursor.js"></script>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<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" href="css/themes/default.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
|
@ -29,7 +28,6 @@
|
|||
<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/searchcursor.js"></script>
|
||||
<script src="lib/codemirror/addon/selection/active-line.js"></script>
|
||||
|
|
|
@ -23,8 +23,9 @@
|
|||
}
|
||||
.CodeMirror-foldmarker {
|
||||
color: var(--cm-foldmarker-ink);
|
||||
font-size: large;
|
||||
text-shadow: none;
|
||||
cursor: pointer;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
.CodeMirror-foldgutter-folded::after {
|
||||
content: '\25B6';
|
||||
|
@ -213,7 +214,7 @@
|
|||
display: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.cm-linter-widget.hasErrors {
|
||||
.cm-linter-widget:not([data-lint="0"]) {
|
||||
display: inline-flex;
|
||||
}
|
||||
.cm-linter-widget .cm-linter-widget-count {
|
||||
|
@ -273,25 +274,44 @@
|
|||
}
|
||||
|
||||
.CodeMirror-lintmarker {
|
||||
background-color: var(--sf-error-ink);
|
||||
height: calc(var(--font-size) - 2px);
|
||||
margin-top: 1px;
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-lintmarker > span {
|
||||
.CodeMirror-lintmarker > * {
|
||||
position: absolute;
|
||||
}
|
||||
.CodeMirror-lintmarker[data-lint="error"] {
|
||||
background-color: var(--sf-error-ink);
|
||||
}
|
||||
.CodeMirror-lintmarker[data-lint="error"] .msg {
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-lintmarker > span {
|
||||
.CodeMirror-lintmarker[data-lint="error"] .msg {
|
||||
background-color: var(--surface-0);
|
||||
border: 1px solid var(--sf-error-ink);
|
||||
color: var(--ink-1);
|
||||
left: 100%;
|
||||
padding: var(--default-gap-xsmall);
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
white-space: pre;
|
||||
}
|
||||
.CodeMirror-lintmarker:hover > span,
|
||||
.CodeMirror-lintmarker > span:hover {
|
||||
.CodeMirror-lintmarker svg {
|
||||
height: 70%;
|
||||
left: 15%;
|
||||
top: 15%;
|
||||
width: 70%;
|
||||
}
|
||||
.CodeMirror-lintmarker[data-fold="start"] {
|
||||
fill: var(--cm-foldmarker-ink);
|
||||
}
|
||||
.CodeMirror-lintmarker[data-fold="start"].folded svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.CodeMirror-lintmarker[data-fold="end"] {
|
||||
fill: var(--border-2);
|
||||
}
|
||||
.CodeMirror-lintmarker[data-lint="error"]:hover > span,
|
||||
.CodeMirror-lintmarker[data-lint="error"] > span:hover {
|
||||
display: initial;
|
||||
}
|
||||
|
|
|
@ -320,6 +320,9 @@ body.mobile.no-tooltips .toolRibbon .tool {
|
|||
border-bottom: 1px dotted #ddd;
|
||||
padding: 0.25em;
|
||||
}
|
||||
#firewall > section .filterExpressions div:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
#firewall > section .filterExpressions span {
|
||||
cursor: default;
|
||||
display: inline-flex;
|
||||
|
|
|
@ -38,9 +38,8 @@ const cmEditor = new CodeMirror(qs$('#userFilters'), {
|
|||
},
|
||||
foldGutter: true,
|
||||
gutters: [
|
||||
'CodeMirror-foldgutter',
|
||||
'CodeMirror-linenumbers',
|
||||
{ className: 'CodeMirror-lintgutter', style: 'width: 10px' },
|
||||
{ className: 'CodeMirror-lintgutter', style: 'width: 11px' },
|
||||
],
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
|
|
|
@ -53,9 +53,8 @@ import './codemirror/ubo-static-filtering.js';
|
|||
autofocus: true,
|
||||
foldGutter: true,
|
||||
gutters: [
|
||||
'CodeMirror-foldgutter',
|
||||
'CodeMirror-linenumbers',
|
||||
{ className: 'CodeMirror-lintgutter', style: 'width: 10px' },
|
||||
{ className: 'CodeMirror-lintgutter', style: 'width: 11px' },
|
||||
],
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import { dom } from '../dom.js';
|
||||
import { dom, qs$ } from '../dom.js';
|
||||
import { i18n$ } from '../i18n.js';
|
||||
|
||||
{
|
||||
|
@ -382,7 +382,9 @@ import { i18n$ } from '../i18n.js';
|
|||
doc.eachLine(start, end, lineHandle => {
|
||||
const markers = lineHandle.gutterMarkers || null;
|
||||
if ( markers === null ) { return; }
|
||||
if ( markers['CodeMirror-lintgutter'] === undefined ) { return; }
|
||||
const marker = markers['CodeMirror-lintgutter'];
|
||||
if ( marker === undefined ) { return; }
|
||||
if ( marker.dataset.lint !== 'error' ) { return; }
|
||||
const line = lineHandle.lineNo();
|
||||
if ( dir < 0 ) {
|
||||
found = line;
|
||||
|
@ -476,7 +478,7 @@ import { i18n$ } from '../i18n.js';
|
|||
'<span class="cm-search-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ' +
|
||||
'<span class="cm-search-widget-count"></span>' +
|
||||
'</span>' +
|
||||
'<span class="cm-linter-widget">' +
|
||||
'<span class="cm-linter-widget" data-lint="0">' +
|
||||
'<span class="cm-linter-widget-count"></span> ' +
|
||||
'<span class="cm-linter-widget-up cm-search-widget-button fa-icon">angle-up</span> ' +
|
||||
'<span class="cm-linter-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ' +
|
||||
|
@ -497,10 +499,12 @@ import { i18n$ } from '../i18n.js';
|
|||
CodeMirror.defineInitHook(function(cm) {
|
||||
getSearchState(cm);
|
||||
cm.on('linterDone', details => {
|
||||
const linterWidget = qs$('.cm-linter-widget');
|
||||
const count = details.errorCount;
|
||||
dom.cl.toggle('.cm-linter-widget', 'hasErrors', count !== 0);
|
||||
if ( linterWidget.dataset.lint === `${count}` ) { return; }
|
||||
linterWidget.dataset.lint = `${count}`;
|
||||
dom.text(
|
||||
'.cm-linter-widget .cm-linter-widget-count',
|
||||
qs$(linterWidget, '.cm-linter-widget-count'),
|
||||
i18n$('linterMainReport').replace('{{count}}', count.toLocaleString())
|
||||
);
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
/******************************************************************************/
|
||||
|
||||
import * as sfp from '../static-filtering-parser.js';
|
||||
import { dom, qs$ } from '../dom.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -670,91 +671,189 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
|
|||
|
||||
const changeset = [];
|
||||
let changesetTimer;
|
||||
|
||||
const includeset = new Set();
|
||||
let errorCount = 0;
|
||||
|
||||
const addChange = (doc, change) => {
|
||||
changeset.push(change);
|
||||
processChangesetAsync(doc);
|
||||
};
|
||||
|
||||
const processChangesetAsync = doc => {
|
||||
if ( changesetTimer !== undefined ) { return; }
|
||||
changesetTimer = self.requestIdleCallback(deadline => {
|
||||
changesetTimer = undefined;
|
||||
processChangeset(doc, deadline);
|
||||
});
|
||||
};
|
||||
|
||||
const extractError = ( ) => {
|
||||
if ( astParser.hasError() === false ) { return; }
|
||||
const error = 'Invalid filter';
|
||||
switch ( astParser.astError ) {
|
||||
case sfp.AST_ERROR_REGEX:
|
||||
return `${error}: Bad regular expression`;
|
||||
case sfp.AST_ERROR_PATTERN:
|
||||
return `${error}: Bad pattern`;
|
||||
case sfp.AST_ERROR_DOMAIN_NAME:
|
||||
return `${error}: Bad domain name`;
|
||||
case sfp.AST_ERROR_OPTION_DUPLICATE:
|
||||
return `${error}: Duplicate filter option`;
|
||||
case sfp.AST_ERROR_OPTION_UNKNOWN:
|
||||
return `${error}: Unsupported filter option`;
|
||||
case sfp.AST_ERROR_IF_TOKEN_UNKNOWN:
|
||||
return `${error}: Unknown preparsing token`;
|
||||
default:
|
||||
break;
|
||||
const extractMarkerDetails = (doc, lineHandle) => {
|
||||
if ( astParser.isUnsupported() ) {
|
||||
return { value: 'error', msg: 'Unsupported filter syntax' };
|
||||
}
|
||||
if ( astParser.isCosmeticFilter() && astParser.result.error ) {
|
||||
return `${error}: ${astParser.result.error}`;
|
||||
if ( astParser.hasError() ) {
|
||||
let msg = 'Invalid filter';
|
||||
switch ( astParser.astError ) {
|
||||
case sfp.AST_ERROR_UNSUPPORTED:
|
||||
msg = `${msg}: Unsupported filter syntax`;
|
||||
break;
|
||||
case sfp.AST_ERROR_REGEX:
|
||||
msg = `${msg}: Bad regular expression`;
|
||||
break;
|
||||
case sfp.AST_ERROR_PATTERN:
|
||||
msg = `${msg}: Bad pattern`;
|
||||
break;
|
||||
case sfp.AST_ERROR_DOMAIN_NAME:
|
||||
msg = `${msg}: Bad domain name`;
|
||||
break;
|
||||
case sfp.AST_ERROR_OPTION_DUPLICATE:
|
||||
msg = `${msg}: Duplicate filter option`;
|
||||
break;
|
||||
case sfp.AST_ERROR_OPTION_UNKNOWN:
|
||||
msg = `${msg}: Unsupported filter option`;
|
||||
break;
|
||||
case sfp.AST_ERROR_IF_TOKEN_UNKNOWN:
|
||||
msg = `${msg}: Unknown preparsing token`;
|
||||
break;
|
||||
default:
|
||||
if ( astParser.isCosmeticFilter() && astParser.result.error ) {
|
||||
msg = `${msg}: ${astParser.result.error}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return { value: 'error', msg };
|
||||
}
|
||||
return error;
|
||||
if ( astParser.astType !== sfp.AST_TYPE_COMMENT ) { return; }
|
||||
if ( astParser.astTypeFlavor !== sfp.AST_TYPE_COMMENT_PREPARSER ) {
|
||||
if ( astParser.raw.startsWith('! <<<<<<<< ') === false ) { return; }
|
||||
for ( const include of includeset ) {
|
||||
if ( astParser.raw.endsWith(include) === false ) { continue; }
|
||||
includeset.delete(include);
|
||||
return { value: 'include-end' };
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( /^\s*!#if \S+/.test(astParser.raw) ) {
|
||||
return { value: 'if-start' };
|
||||
}
|
||||
if ( /^\s*!#endif\b/.test(astParser.raw) ) {
|
||||
return { value: 'if-end' };
|
||||
}
|
||||
const match = /^\s*!#include\s*(\S+)/.exec(astParser.raw);
|
||||
if ( match === null ) { return; }
|
||||
const nextLineHandle = doc.getLineHandle(lineHandle.lineNo() + 1);
|
||||
if ( nextLineHandle === undefined ) { return; }
|
||||
if ( nextLineHandle.text.startsWith('! >>>>>>>> ') === false ) { return; }
|
||||
const includeToken = `/${match[1]}`;
|
||||
if ( nextLineHandle.text.endsWith(includeToken) === false ) { return; }
|
||||
includeset.add(includeToken);
|
||||
return { value: 'include-start' };
|
||||
};
|
||||
|
||||
const extractMarker = lineHandle => {
|
||||
const markers = lineHandle.gutterMarkers || null;
|
||||
if ( markers === null ) { return; }
|
||||
return markers['CodeMirror-lintgutter'] || undefined;
|
||||
return markers !== null
|
||||
? markers['CodeMirror-lintgutter'] || null
|
||||
: null;
|
||||
};
|
||||
|
||||
const markerTemplate = (( ) => {
|
||||
const marker = document.createElement('div');
|
||||
marker.classList.add('CodeMirror-lintmarker');
|
||||
marker.textContent = '\xA0';
|
||||
const info = document.createElement('span');
|
||||
marker.append(info);
|
||||
return marker;
|
||||
})();
|
||||
const markerTemplates = {
|
||||
'error': {
|
||||
node: null,
|
||||
html: [
|
||||
'<div class="CodeMirror-lintmarker" data-lint="error"> ',
|
||||
'<span class="msg"></span>',
|
||||
'</div>',
|
||||
],
|
||||
},
|
||||
'if-start': {
|
||||
node: null,
|
||||
html: [
|
||||
'<div class="CodeMirror-lintmarker" data-lint="if" data-fold="start"> ',
|
||||
'<svg viewBox="0 0 100 100">',
|
||||
'<polygon points="0,0 100,0 50,100" />',
|
||||
'</svg>',
|
||||
'</div>',
|
||||
],
|
||||
},
|
||||
'if-end': {
|
||||
node: null,
|
||||
html: [
|
||||
'<div class="CodeMirror-lintmarker" data-lint="if" data-fold="end"> ',
|
||||
'<svg viewBox="0 0 100 100">',
|
||||
'<polygon points="50,0 100,100 0,100" />',
|
||||
'</svg>',
|
||||
'</div>',
|
||||
],
|
||||
},
|
||||
'include-start': {
|
||||
node: null,
|
||||
html: [
|
||||
'<div class="CodeMirror-lintmarker" data-lint="include" data-fold="start"> ',
|
||||
'<svg viewBox="0 0 100 100">',
|
||||
'<polygon points="0,0 100,0 50,100" />',
|
||||
'</svg>',
|
||||
'</div>',
|
||||
],
|
||||
},
|
||||
'include-end': {
|
||||
node: null,
|
||||
html: [
|
||||
'<div class="CodeMirror-lintmarker" data-lint="include" data-fold="end"> ',
|
||||
'<svg viewBox="0 0 100 100">',
|
||||
'<polygon points="50,0 100,100 0,100" />',
|
||||
'</svg>',
|
||||
'</div>',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const makeMarker = (doc, lineHandle, marker, error) => {
|
||||
if ( marker === undefined ) {
|
||||
marker = addMarker(doc, lineHandle);
|
||||
const markerFromTemplate = which => {
|
||||
const template = markerTemplates[which];
|
||||
if ( template.node === null ) {
|
||||
const domParser = new DOMParser();
|
||||
const doc = domParser.parseFromString(template.html.join(''), 'text/html');
|
||||
template.node = document.adoptNode(qs$(doc, '.CodeMirror-lintmarker'));
|
||||
}
|
||||
marker.children[0].textContent = error;
|
||||
return template.node.cloneNode(true);
|
||||
};
|
||||
|
||||
const addMarker = (doc, lineHandle) => {
|
||||
const marker = markerTemplate.cloneNode(true);
|
||||
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', marker);
|
||||
lineHandle.on('delete', deleteMarker);
|
||||
errorCount += 1;
|
||||
return marker;
|
||||
const addMarker = (doc, lineHandle, marker, details) => {
|
||||
if ( marker !== null && marker.dataset.lint !== details.value ) {
|
||||
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', null);
|
||||
if ( marker.dataset.lint === 'error' ) {
|
||||
errorCount -= 1;
|
||||
}
|
||||
marker = null;
|
||||
}
|
||||
if ( marker === null ) {
|
||||
marker = markerFromTemplate(details.value);
|
||||
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', marker);
|
||||
if ( details.value === 'error' ) {
|
||||
errorCount += 1;
|
||||
}
|
||||
}
|
||||
const msgElem = qs$(marker, '.msg');
|
||||
if ( msgElem === null ) { return; }
|
||||
msgElem.textContent = details.msg || '';
|
||||
};
|
||||
|
||||
const deleteMarker = ( ) => {
|
||||
errorCount -= 1;
|
||||
const removeMarker = (doc, lineHandle, marker) => {
|
||||
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', null);
|
||||
if ( marker.dataset.lint === 'error' ) {
|
||||
errorCount -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
const processChange = (doc, deadline, change) => {
|
||||
const processDeletion = (doc, change) => {
|
||||
let { from, to } = change;
|
||||
doc.eachLine(from.line, to.line, lineHandle => {
|
||||
const marker = extractMarker(lineHandle);
|
||||
if ( marker === null ) { return; }
|
||||
if ( marker.dataset.lint === 'error' ) {
|
||||
errorCount -= 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const processInsertion = (doc, deadline, change) => {
|
||||
let { from, to } = change;
|
||||
doc.eachLine(from, to, lineHandle => {
|
||||
astParser.parse(lineHandle.text);
|
||||
const error = extractError();
|
||||
const markerDetails = extractMarkerDetails(doc, lineHandle);
|
||||
const marker = extractMarker(lineHandle);
|
||||
if ( error === undefined && marker ) {
|
||||
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', null);
|
||||
deleteMarker();
|
||||
} else if ( error !== undefined ) {
|
||||
makeMarker(doc, lineHandle, marker, error);
|
||||
if ( markerDetails === undefined && marker !== null ) {
|
||||
removeMarker(doc, lineHandle, marker);
|
||||
} else if ( markerDetails !== undefined ) {
|
||||
addMarker(doc, lineHandle, marker, markerDetails);
|
||||
}
|
||||
from += 1;
|
||||
if ( (from & 0x0F) !== 0 ) { return; }
|
||||
|
@ -770,7 +869,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
|
|||
const cm = doc.getEditor();
|
||||
cm.startOperation();
|
||||
while ( changeset.length !== 0 ) {
|
||||
const change = processChange(doc, deadline, changeset.shift());
|
||||
const change = processInsertion(doc, deadline, changeset.shift());
|
||||
if ( change === undefined ) { continue; }
|
||||
changeset.unshift(change);
|
||||
break;
|
||||
|
@ -779,17 +878,120 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
|
|||
if ( changeset.length !== 0 ) {
|
||||
return processChangesetAsync(doc);
|
||||
}
|
||||
includeset.clear();
|
||||
CodeMirror.signal(doc.getEditor(), 'linterDone', { errorCount });
|
||||
};
|
||||
|
||||
CodeMirror.defineInitHook(cm => {
|
||||
cm.on('changes', function(cm, changes) {
|
||||
const doc = cm.getDoc();
|
||||
for ( const change of changes ) {
|
||||
const from = change.from.line;
|
||||
const to = from + change.text.length;
|
||||
addChange(doc, { from, to });
|
||||
const processChangesetAsync = doc => {
|
||||
if ( changesetTimer !== undefined ) { return; }
|
||||
changesetTimer = self.requestIdleCallback(deadline => {
|
||||
changesetTimer = undefined;
|
||||
processChangeset(doc, deadline);
|
||||
});
|
||||
};
|
||||
|
||||
const onChanges = (cm, changes) => {
|
||||
const doc = cm.getDoc();
|
||||
for ( const change of changes ) {
|
||||
const from = change.from.line;
|
||||
const to = from + change.text.length;
|
||||
changeset.push({ from, to });
|
||||
processChangesetAsync(doc);
|
||||
}
|
||||
};
|
||||
|
||||
const onBeforeChanges = (cm, change) => {
|
||||
const doc = cm.getDoc();
|
||||
processDeletion(doc, change);
|
||||
};
|
||||
|
||||
const foldRangeFinder = (cm, from) => {
|
||||
const lineNo = from.line;
|
||||
const lineHandle = cm.getDoc().getLineHandle(lineNo);
|
||||
const marker = extractMarker(lineHandle);
|
||||
if ( marker === null ) { return; }
|
||||
if ( marker.dataset.fold === undefined ) { return; }
|
||||
const foldName = marker.dataset.lint;
|
||||
from.ch = lineHandle.text.length;
|
||||
const to = { line: 0, ch: 0 };
|
||||
const doc = cm.getDoc();
|
||||
let depth = 0;
|
||||
doc.eachLine(from.line, doc.lineCount(), lineHandle => {
|
||||
const marker = extractMarker(lineHandle);
|
||||
if ( marker === null ) { return; }
|
||||
if ( marker.dataset.lint === foldName && marker.dataset.fold === 'start' ) {
|
||||
depth += 1;
|
||||
return;
|
||||
}
|
||||
if ( marker.dataset.lint !== foldName ) { return; }
|
||||
if ( marker.dataset.fold !== 'end' ) { return; }
|
||||
depth -= 1;
|
||||
if ( depth !== 0 ) { return; }
|
||||
to.line = lineHandle.lineNo();
|
||||
return true;
|
||||
});
|
||||
return { from, to };
|
||||
};
|
||||
|
||||
const onGutterClick = (cm, lineNo, gutterId, ev) => {
|
||||
if ( ev.button !== 0 ) { return; }
|
||||
if ( gutterId !== 'CodeMirror-lintgutter' ) { return; }
|
||||
const doc = cm.getDoc();
|
||||
const lineHandle = doc.getLineHandle(lineNo);
|
||||
const marker = extractMarker(lineHandle);
|
||||
if ( marker === null ) { return; }
|
||||
if ( marker.dataset.fold === 'start' ) {
|
||||
if ( ev.ctrlKey ) {
|
||||
if ( dom.cl.has(marker, 'folded') ) {
|
||||
CodeMirror.commands.unfoldAll(cm);
|
||||
} else {
|
||||
CodeMirror.commands.foldAll(cm);
|
||||
}
|
||||
doc.setCursor(lineNo);
|
||||
return;
|
||||
}
|
||||
cm.foldCode(lineNo, {
|
||||
widget: '\u00A0\u22EF\u00A0',
|
||||
rangeFinder: foldRangeFinder,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if ( marker.dataset.fold === 'end' ) {
|
||||
let depth = 1;
|
||||
let lineNo = lineHandle.lineNo();
|
||||
while ( lineNo-- ) {
|
||||
const prevLineHandle = doc.getLineHandle(lineNo);
|
||||
const markerFrom = extractMarker(prevLineHandle);
|
||||
if ( markerFrom === null ) { continue; }
|
||||
if ( markerFrom.dataset.fold === 'end' ) {
|
||||
depth += 1;
|
||||
} else if ( markerFrom.dataset.fold === 'start' ) {
|
||||
depth -= 1;
|
||||
if ( depth === 0 ) {
|
||||
doc.setCursor(lineNo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
CodeMirror.defineInitHook(cm => {
|
||||
cm.on('changes', onChanges);
|
||||
cm.on('beforeChange', onBeforeChanges);
|
||||
cm.on('gutterClick', onGutterClick);
|
||||
cm.on('fold', function(cm, from) {
|
||||
const doc = cm.getDoc();
|
||||
const lineHandle = doc.getLineHandle(from.line);
|
||||
const marker = extractMarker(lineHandle);
|
||||
dom.cl.add(marker, 'folded');
|
||||
});
|
||||
cm.on('unfold', function(cm, from) {
|
||||
const doc = cm.getDoc();
|
||||
const lineHandle = doc.getLineHandle(from.line);
|
||||
const marker = extractMarker(lineHandle);
|
||||
dom.cl.remove(marker, 'folded');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -727,6 +727,7 @@ export class AstFilterParser {
|
|||
this.astType = AST_TYPE_NONE;
|
||||
this.astTypeFlavor = AST_TYPE_NONE;
|
||||
this.astFlags = 0;
|
||||
this.astError = 0;
|
||||
this.nodeTypeRegister = [];
|
||||
this.nodeTypeRegisterPtr = 0;
|
||||
this.nodeTypeLookupTable = new Uint32Array(NODE_TYPE_COUNT);
|
||||
|
@ -1068,7 +1069,7 @@ export class AstFilterParser {
|
|||
const head = this.allocTypedNode(NODE_TYPE_NET_RAW, parentBeg, tailStart);
|
||||
if ( this.linkDown(head, this.parseNet(head)) === 0 ) {
|
||||
this.astType = AST_TYPE_UNKNOWN;
|
||||
this.addFlags(AST_FLAG_UNSUPPORTED);
|
||||
this.addFlags(AST_FLAG_UNSUPPORTED | AST_FLAG_HAS_ERROR);
|
||||
}
|
||||
if ( tail !== 0 ) {
|
||||
this.linkRight(head, tail);
|
||||
|
@ -2085,7 +2086,7 @@ export class AstFilterParser {
|
|||
if ( c === 0x40 /* @ */ ) {
|
||||
return AST_FLAG_IS_EXCEPTION | this.extFlagsFromAnchor(anchorBeg+1);
|
||||
}
|
||||
return AST_FLAG_UNSUPPORTED;
|
||||
return AST_FLAG_UNSUPPORTED | AST_FLAG_HAS_ERROR;
|
||||
}
|
||||
|
||||
validateExt() {
|
||||
|
@ -2395,6 +2396,10 @@ export class AstFilterParser {
|
|||
return (this.astFlags & AST_FLAG_HAS_ERROR) !== 0;
|
||||
}
|
||||
|
||||
isUnsupported() {
|
||||
return (this.astFlags & AST_FLAG_UNSUPPORTED) !== 0;
|
||||
}
|
||||
|
||||
hasOptions() {
|
||||
return (this.astFlags & AST_FLAG_HAS_OPTIONS) !== 0;
|
||||
}
|
||||
|
@ -3015,7 +3020,7 @@ class ExtSelectorCompiler {
|
|||
this.reIsRelativeSelector = /^\s*[+>~]/;
|
||||
this.reExtendedSyntax = /\[-(?:abp|ext)-[a-z-]+=(['"])(?:.+?)(?:\1)\]/;
|
||||
this.reExtendedSyntaxReplacer = /\[-(?:abp|ext)-([a-z-]+)=(['"])(.+?)\2\]/g;
|
||||
this.abpProceduralOpReplacer = /:-abp-(?:contains|has)\(/g;
|
||||
this.abpProceduralOpReplacer = /:-abp-(?:[a-z]+)\(/g;
|
||||
this.nativeCssHas = instanceOptions.nativeCssHas === true;
|
||||
// https://www.w3.org/TR/css-syntax-3/#typedef-ident-token
|
||||
this.reInvalidIdentifier = /^\d/;
|
||||
|
@ -3048,15 +3053,14 @@ class ExtSelectorCompiler {
|
|||
return `:${op}(${a3})`;
|
||||
});
|
||||
} else {
|
||||
let asProcedural = false;
|
||||
raw = raw.replace(this.abpProceduralOpReplacer, match => {
|
||||
if ( match === ':-abp-contains(' ) {
|
||||
return ':has-text(';
|
||||
} else if ( match === ':-abp-has(' ) {
|
||||
this.asProcedural = false;
|
||||
return ':has(';
|
||||
}
|
||||
if ( match === ':-abp-contains(' ) { return ':has-text('; }
|
||||
if ( match === ':-abp-has(' ) { return ':has('; }
|
||||
asProcedural = true;
|
||||
return match;
|
||||
});
|
||||
this.asProcedural = asProcedural;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3068,9 +3072,10 @@ class ExtSelectorCompiler {
|
|||
return true;
|
||||
}
|
||||
|
||||
this.error = undefined;
|
||||
out.compiled = this.compileSelector(raw);
|
||||
if ( out.compiled === undefined ) {
|
||||
out.error = this.error || undefined;
|
||||
out.error = this.error;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3116,7 +3121,13 @@ class ExtSelectorCompiler {
|
|||
parseValue: false,
|
||||
});
|
||||
} catch(reason) {
|
||||
this.error = reason && reason.message || undefined;
|
||||
const lines = [ reason.message ];
|
||||
const extra = reason.sourceFragment().split('\n');
|
||||
if ( extra.length !== 0 ) { lines.push(''); }
|
||||
const match = /^[^|]+\|/.exec(extra[0]);
|
||||
const beg = match !== null ? match[0].length : 0;
|
||||
lines.push(...extra.map(a => a.slice(beg)));
|
||||
this.error = lines.join('\n');
|
||||
return;
|
||||
}
|
||||
const parts = [];
|
||||
|
@ -3177,6 +3188,7 @@ class ExtSelectorCompiler {
|
|||
}
|
||||
if ( data.type !== 'PseudoClassSelector' ) { return; }
|
||||
if ( data.name.startsWith('-abp-') && this.asProcedural === false ) {
|
||||
this.error = `${data.name} requires '#?#' separator syntax`;
|
||||
return;
|
||||
}
|
||||
// Post-analysis, mind:
|
||||
|
@ -3189,6 +3201,7 @@ class ExtSelectorCompiler {
|
|||
data.type = 'ActionSelector';
|
||||
} else if ( data.name.startsWith('-abp-') ) {
|
||||
data.type = 'Error';
|
||||
this.error = `${data.name} is not supported`;
|
||||
return;
|
||||
}
|
||||
if ( this.maybeProceduralOperatorNames.has(data.name) === false ) {
|
||||
|
@ -3196,6 +3209,7 @@ class ExtSelectorCompiler {
|
|||
}
|
||||
if ( this.astHasType(args, 'ActionSelector') ) {
|
||||
data.type = 'Error';
|
||||
this.error = 'invalid use of action operator';
|
||||
return;
|
||||
}
|
||||
if ( this.astHasType(args, 'ProceduralSelector') ) {
|
||||
|
|
|
@ -152,12 +152,12 @@ function showData() {
|
|||
if ( reportedPage !== null ) {
|
||||
shownData.popupPanel = reportedPage.popupPanel;
|
||||
}
|
||||
const text = JSON.stringify(shownData, null, 2)
|
||||
const text = JSON.stringify(shownData, null, 1)
|
||||
.split('\n')
|
||||
.slice(1, -1)
|
||||
.map(v => {
|
||||
return v
|
||||
.replace(/^( *?) "/, '$1')
|
||||
.replace(/^( *?) "/, '$1')
|
||||
.replace(/^( *.*[^\\])(?:": "|": \{$|": \[$|": )/, '$1: ')
|
||||
.replace(/(?:",?|\},?|\],?|,)$/, '');
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue