diff --git a/src/css/codemirror.css b/src/css/codemirror.css
index d7b5d7410f558..2d15bf8d193dd 100644
--- a/src/css/codemirror.css
+++ b/src/css/codemirror.css
@@ -289,16 +289,14 @@
.CodeMirror-lintmarker > * {
position: absolute;
}
-.CodeMirror-lintmarker[data-lint="error"] {
+.CodeMirror-lintmarker[data-error="y"] {
background-color: var(--sf-error-ink);
}
-.CodeMirror-lintmarker[data-lint="error"] .msg {
- display: none;
- }
-.CodeMirror-lintmarker[data-lint="error"] .msg {
+.CodeMirror-lintmarker .msg {
background-color: var(--surface-0);
border: 1px solid var(--sf-error-ink);
color: var(--ink-1);
+ display: none;
filter: drop-shadow(2px 2px 4px #0008);
left: 100%;
padding: var(--default-gap-xsmall);
@@ -311,6 +309,9 @@
top: 15%;
width: 70%;
}
+.CodeMirror-lintmarker[data-error="y"] svg {
+ display: none;
+ }
.CodeMirror-lintmarker[data-fold="start"] {
fill: var(--cm-foldmarker-ink);
}
@@ -320,7 +321,7 @@
.CodeMirror-lintmarker[data-fold="end"] {
fill: var(--border-2);
}
-.CodeMirror-lintmarker[data-lint="error"]:hover > span,
-.CodeMirror-lintmarker[data-lint="error"] > span:hover {
+.CodeMirror-lintmarker[data-error="y"]:hover > span,
+.CodeMirror-lintmarker[data-error="y"] > span:hover {
display: initial;
}
diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js
index df39ee15e5635..477e9ccd0b61b 100644
--- a/src/js/codemirror/search.js
+++ b/src/js/codemirror/search.js
@@ -377,7 +377,7 @@ import { i18n$ } from '../i18n.js';
if ( markers === null ) { return; }
const marker = markers['CodeMirror-lintgutter'];
if ( marker === undefined ) { return; }
- if ( marker.dataset.lint !== 'error' ) { return; }
+ if ( marker.dataset.error !== 'y' ) { return; }
const line = lineHandle.lineNo();
if ( dir < 0 ) {
found = line;
diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js
index c2e84da50397b..ac1b048a8b815 100644
--- a/src/js/codemirror/ubo-static-filtering.js
+++ b/src/js/codemirror/ubo-static-filtering.js
@@ -699,9 +699,12 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
const includeset = new Set();
let errorCount = 0;
+ const ifendifSet = new Set();
+ let ifendifSetChanged = false;
+
const extractMarkerDetails = (doc, lineHandle) => {
if ( astParser.isUnsupported() ) {
- return { value: 'error', msg: 'Unsupported filter syntax' };
+ return { lint: 'error', msg: 'Unsupported filter syntax' };
}
if ( astParser.hasError() ) {
let msg = 'Invalid filter';
@@ -739,7 +742,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
}
break;
}
- return { value: 'error', msg };
+ return { lint: 'error', msg };
}
if ( astParser.astType !== sfp.AST_TYPE_COMMENT ) { return; }
if ( astParser.astTypeFlavor !== sfp.AST_TYPE_COMMENT_PREPARSER ) {
@@ -747,15 +750,23 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
for ( const include of includeset ) {
if ( astParser.raw.endsWith(include) === false ) { continue; }
includeset.delete(include);
- return { value: 'include-end' };
+ return { lint: 'include-end' };
}
return;
}
if ( /^\s*!#if \S+/.test(astParser.raw) ) {
- return { value: 'if-start' };
+ return {
+ lint: 'if-start',
+ data: {
+ state: sfp.utils.preparser.evaluateExpr(
+ astParser.getTypeString(sfp.NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE),
+ preparseDirectiveEnv
+ ) ? 'y' : 'n'
+ }
+ };
}
if ( /^\s*!#endif\b/.test(astParser.raw) ) {
- return { value: 'if-end' };
+ return { lint: 'if-end' };
}
const match = /^\s*!#include\s*(\S+)/.exec(astParser.raw);
if ( match === null ) { return; }
@@ -765,7 +776,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
const includeToken = `/${match[1]}`;
if ( nextLineHandle.text.endsWith(includeToken) === false ) { return; }
includeset.add(includeToken);
- return { value: 'include-start' };
+ return { lint: 'include-start' };
};
const extractMarker = lineHandle => {
@@ -779,7 +790,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
'error': {
node: null,
html: [
- '
',
+ '
',
'',
'
',
],
@@ -787,10 +798,11 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
'if-start': {
node: null,
html: [
- '
',
+ '
',
'
',
+ '
Mismatched if-endif directive',
'
',
],
},
@@ -801,6 +813,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
'
',
+ '
Mismatched if-endif directive',
'
',
],
},
@@ -826,41 +839,98 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
},
};
- const markerFromTemplate = which => {
- const template = markerTemplates[which];
+ const markerFromTemplate = details => {
+ const template = markerTemplates[details.lint];
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'));
}
- return template.node.cloneNode(true);
+ const node = template.node.cloneNode(true);
+ if ( details.data instanceof Object ) {
+ for ( const [ k, v ] of Object.entries(details.data) ) {
+ node.dataset[k] = `${v}`;
+ }
+ }
+ return node;
};
const addMarker = (doc, lineHandle, marker, details) => {
- if ( marker !== null && marker.dataset.lint !== details.value ) {
+ if ( marker && marker.dataset.lint !== details.lint ) {
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', null);
- if ( marker.dataset.lint === 'error' ) {
+ if ( marker.dataset.error === 'y' ) {
errorCount -= 1;
}
+ if ( marker.dataset.lint === 'if' ) {
+ ifendifSet.delete(lineHandle);
+ ifendifSetChanged = true;
+ }
marker = null;
}
if ( marker === null ) {
- marker = markerFromTemplate(details.value);
+ marker = markerFromTemplate(details);
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', marker);
- if ( details.value === 'error' ) {
+ if ( marker.dataset.error === 'y' ) {
errorCount += 1;
}
+ if ( marker.dataset.lint === 'if' ) {
+ ifendifSet.add(lineHandle);
+ ifendifSetChanged = true;
+ }
}
+ if ( typeof details.msg !== 'string' || details.msg === '' ) { return; }
const msgElem = qs$(marker, '.msg');
if ( msgElem === null ) { return; }
- msgElem.textContent = details.msg || '';
+ msgElem.textContent = details.msg;
};
const removeMarker = (doc, lineHandle, marker) => {
doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', null);
- if ( marker.dataset.lint === 'error' ) {
+ if ( marker.dataset.error === 'y' ) {
errorCount -= 1;
}
+ if ( marker.dataset.lint === 'if' ) {
+ ifendifSet.delete(lineHandle);
+ ifendifSetChanged = true;
+ }
+ };
+
+ // Analyze whether all if-endif are properly paired
+ const processIfendifs = ( ) => {
+ if ( ifendifSet.size === 0 ) { return; }
+ if ( ifendifSetChanged !== true ) { return; }
+ const sortFn = (a, b) => a.lineNo() - b.lineNo();
+ const sorted = Array.from(ifendifSet).sort(sortFn);
+ const bad = [];
+ const stack = [];
+ for ( const line of sorted ) {
+ const marker = extractMarker(line);
+ const fold = marker.dataset.fold;
+ if ( fold === 'start' ) {
+ stack.push(line);
+ } else if ( fold === 'end' ) {
+ if ( stack.length !== 0 ) {
+ if ( marker.dataset.error === 'y' ) {
+ marker.dataset.error = '';
+ errorCount -= 1;
+ }
+ const ifstart = extractMarker(stack.pop());
+ if ( ifstart.dataset.error === 'y' ) {
+ ifstart.dataset.error = '';
+ errorCount -= 1;
+ }
+ } else {
+ bad.push(line);
+ }
+ }
+ }
+ bad.push(...stack);
+ for ( const line of bad ) {
+ const marker = extractMarker(line);
+ marker.dataset.error = 'y';
+ errorCount += 1;
+ }
+ ifendifSetChanged = false;
};
const processDeletion = (doc, change) => {
@@ -868,10 +938,12 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
doc.eachLine(from.line, to.line, lineHandle => {
const marker = extractMarker(lineHandle);
if ( marker === null ) { return; }
- if ( marker.dataset.lint === 'error' ) {
+ if ( marker.dataset.error === 'y' ) {
+ marker.dataset.error = '';
errorCount -= 1;
- marker.dataset.lint = 'void';
}
+ ifendifSet.delete(lineHandle);
+ ifendifSetChanged = true;
});
};
@@ -881,10 +953,10 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
astParser.parse(lineHandle.text);
const markerDetails = extractMarkerDetails(doc, lineHandle);
const marker = extractMarker(lineHandle);
- if ( markerDetails === undefined && marker !== null ) {
- removeMarker(doc, lineHandle, marker);
- } else if ( markerDetails !== undefined ) {
+ if ( markerDetails !== undefined ) {
addMarker(doc, lineHandle, marker, markerDetails);
+ } else if ( marker !== null ) {
+ removeMarker(doc, lineHandle, marker);
}
from += 1;
if ( (from & 0x0F) !== 0 ) { return; }
@@ -910,6 +982,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
return processChangesetAsync(doc);
}
includeset.clear();
+ processIfendifs(doc);
CodeMirror.signal(doc.getEditor(), 'linterDone', { errorCount });
};
@@ -922,13 +995,14 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
};
const onChanges = (cm, changes) => {
+ if ( changes.length === 0 ) { return; }
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);
}
+ processChangesetAsync(doc);
};
const onBeforeChanges = (cm, change) => {
diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js
index f5106bfbd3820..eb8988bf64f54 100644
--- a/src/js/static-filtering-parser.js
+++ b/src/js/static-filtering-parser.js
@@ -1224,6 +1224,7 @@ export class AstFilterParser {
? NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE
: NODE_TYPE_PREPARSE_DIRECTIVE_VALUE;
const next = this.allocTypedNode(type, directiveEnd, parentEnd);
+ this.addNodeToRegister(type, next);
this.linkRight(head, next);
if ( type === NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE ) {
const rawToken = this.getNodeString(next).trim();