diff --git a/src/1p-filters.html b/src/1p-filters.html
index fbb016f57..13fc9792f 100644
--- a/src/1p-filters.html
+++ b/src/1p-filters.html
@@ -38,6 +38,8 @@
+
+
diff --git a/src/asset-viewer.html b/src/asset-viewer.html
index b54ec865b..42874e0e2 100644
--- a/src/asset-viewer.html
+++ b/src/asset-viewer.html
@@ -30,6 +30,7 @@ body {
+
diff --git a/src/css/codemirror.css b/src/css/codemirror.css
index d31e7d6d4..182551b4d 100644
--- a/src/css/codemirror.css
+++ b/src/css/codemirror.css
@@ -48,6 +48,14 @@
.cm-staticnetAllow { color: #004f00; }
.cm-staticOpt { background-color: #ddd; font-weight: bold; }
+div.CodeMirror span.CodeMirror-matchingbracket {
+ color: unset;
+ }
+.CodeMirror-matchingbracket {
+ color: inherit !important;
+ font-weight: bold;
+ }
+
.cm-search-widget {
align-items: center;
background-color: var(--bg-code);
diff --git a/src/js/1p-filters.js b/src/js/1p-filters.js
index 28217dd5a..9a3894177 100644
--- a/src/js/1p-filters.js
+++ b/src/js/1p-filters.js
@@ -29,15 +29,15 @@
/******************************************************************************/
-const cmEditor = new CodeMirror(
- document.getElementById('userFilters'),
- {
- autofocus: true,
- lineNumbers: true,
- lineWrapping: true,
- styleActiveLine: true,
- }
-);
+const cmEditor = new CodeMirror(document.getElementById('userFilters'), {
+ autoCloseBrackets: true,
+ autofocus: true,
+ lineNumbers: true,
+ lineWrapping: true,
+ matchBrackets: true,
+ maxScanLines: 1,
+ styleActiveLine: true,
+});
uBlockDashboard.patchCodeMirrorEditor(cmEditor);
diff --git a/src/js/asset-viewer.js b/src/js/asset-viewer.js
index 8ea3eb5a5..30c64f301 100644
--- a/src/js/asset-viewer.js
+++ b/src/js/asset-viewer.js
@@ -30,16 +30,15 @@
const assetKey = params.get('url');
if ( assetKey === null ) { return; }
- const cmEditor = new CodeMirror(
- document.getElementById('content'),
- {
- autofocus: true,
- lineNumbers: true,
- lineWrapping: true,
- readOnly: true,
- styleActiveLine: true,
- }
- );
+ const cmEditor = new CodeMirror(document.getElementById('content'), {
+ autofocus: true,
+ lineNumbers: true,
+ lineWrapping: true,
+ matchBrackets: true,
+ maxScanLines: 1,
+ readOnly: true,
+ styleActiveLine: true,
+ });
uBlockDashboard.patchCodeMirrorEditor(cmEditor);
diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js
index a762536e6..ae5fe9492 100644
--- a/src/js/codemirror/ubo-static-filtering.js
+++ b/src/js/codemirror/ubo-static-filtering.js
@@ -29,6 +29,48 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
let parserSlot = 0;
let netOptionValueMode = false;
+ const colorExtHTMLPatternSpan = function(stream) {
+ const { i } = parser.patternSpan;
+ if ( stream.pos === parser.slices[i+1] ) {
+ stream.pos += 1;
+ return 'def';
+ }
+ stream.skipToEnd();
+ return 'variable';
+ };
+
+ const colorExtScriptletPatternSpan = function(stream) {
+ const { i, len } = parser.patternSpan;
+ if ( stream.pos === parser.slices[i+1] ) {
+ stream.pos += 4;
+ return 'def';
+ }
+ if ( len > 3 ) {
+ const r = parser.slices[i+len+1] - 1;
+ if ( stream.pos < r ) {
+ stream.pos = r;
+ return 'variable';
+ }
+ if ( stream.pos === r ) {
+ stream.pos += 1;
+ return 'def';
+ }
+ }
+ stream.skipToEnd();
+ return 'variable';
+ };
+
+ const colorExtPatternSpan = function(stream) {
+ if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) {
+ return colorExtScriptletPatternSpan(stream);
+ }
+ if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) {
+ return colorExtHTMLPatternSpan(stream);
+ }
+ stream.skipToEnd();
+ return 'variable';
+ };
+
const colorExtSpan = function(stream) {
if ( parserSlot < parser.optionsAnchorSpan.i ) {
const style = (parser.slices[parserSlot] & parser.BITComma) === 0
@@ -49,23 +91,25 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
parserSlot += 3;
return `${style} strong`;
}
- if ( parserSlot >= parser.patternSpan.i ) {
- stream.skipToEnd();
- return 'variable';
+ if (
+ parserSlot >= parser.patternSpan.i &&
+ parserSlot < parser.rightSpaceSpan.i
+ ) {
+ return colorExtPatternSpan(stream);
}
stream.skipToEnd();
- return '';
+ return null;
};
const colorNetSpan = function(stream) {
if ( parserSlot < parser.exceptionSpan.i ) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
- return '';
+ return null;
}
if (
parserSlot === parser.exceptionSpan.i &&
- parser.exceptionSpan.l !== 0
+ parser.exceptionSpan.len !== 0
) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
@@ -73,9 +117,9 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
}
if (
parserSlot === parser.patternLeftAnchorSpan.i &&
- parser.patternLeftAnchorSpan.l !== 0 ||
+ parser.patternLeftAnchorSpan.len !== 0 ||
parserSlot === parser.patternRightAnchorSpan.i &&
- parser.patternRightAnchorSpan.l !== 0
+ parser.patternRightAnchorSpan.len !== 0
) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
@@ -83,31 +127,30 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
}
if (
parserSlot >= parser.patternSpan.i &&
- parserSlot < parser.patternRightAnchorSpan.i
+ parserSlot < parser.optionsAnchorSpan.i
) {
- const isRegex = parser.patternIsRegex();
- if (
- (isRegex === false) &&
- (parser.slices[parserSlot] & (parser.BITAsterisk | parser.BITCaret)) !== 0
- ) {
+ if ( parser.patternIsRegex() ) {
+ stream.pos = parser.slices[parser.optionsAnchorSpan.i+1];
+ parserSlot += parser.optionsAnchorSpan.i;
+ return 'variable regex';
+ }
+ if ( (parser.slices[parserSlot] & (parser.BITAsterisk | parser.BITCaret)) !== 0 ) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
return 'keyword strong';
}
- let style = 'variable';
- if ( isRegex ) { style += ' regex'; }
const nextSlot = parser.skipUntil(
- parserSlot,
+ parserSlot + 3,
parser.patternRightAnchorSpan.i,
parser.BITAsterisk | parser.BITCaret
);
stream.pos = parser.slices[nextSlot+1];
parserSlot = nextSlot;
- return style;
+ return 'variable';
}
if (
parserSlot === parser.optionsAnchorSpan.i &&
- parser.optionsAnchorSpan.l !== 0
+ parserSlot < parser.optionsSpan.i !== 0
) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
@@ -115,7 +158,7 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
}
if (
parserSlot >= parser.optionsSpan.i &&
- parser.optionsSpan.l !== 0
+ parserSlot < parser.rightSpaceSpan.i
) {
const bits = parser.slices[parserSlot];
let style;
@@ -137,13 +180,13 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
}
if (
parserSlot >= parser.commentSpan.i &&
- parser.commentSpan.l !== 0
+ parser.commentSpan.len !== 0
) {
stream.skipToEnd();
return 'comment';
}
stream.skipToEnd();
- return '';
+ return null;
};
const colorSpan = function(stream) {
@@ -185,7 +228,7 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
parserSlot = 0;
netOptionValueMode = false;
}
- let style = colorSpan(stream);
+ let style = colorSpan(stream) || '';
if ( (parser.flavorBits & parser.BITFlavorError) !== 0 ) {
style += ' line-background-error';
}
diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js
index 47b78ae0b..0b3da5679 100644
--- a/src/js/static-filtering-parser.js
+++ b/src/js/static-filtering-parser.js
@@ -129,7 +129,7 @@ const Parser = class {
analyze(raw) {
this.slice(raw);
- let slot = this.leftSpaceSpan.l;
+ let slot = this.leftSpaceSpan.len;
if ( slot === this.rightSpaceSpan.i ) { return; }
// test for `!`, `#`, or `[`
@@ -158,7 +158,7 @@ const Parser = class {
for (;;) {
if ( hasBits(this.slices[hashSlot-3], BITSpace) ) {
this.commentSpan.i = hashSlot-3;
- this.commentSpan.l = this.rightSpaceSpan.i - hashSlot;
+ this.commentSpan.len = this.rightSpaceSpan.i - hashSlot;
break;
}
hashSlot = this.findFirstMatch(hashSlot + 6, BITHash);
@@ -194,25 +194,25 @@ const Parser = class {
analyzeExt(from) {
let end = this.rightSpaceSpan.i;
// Number of consecutive #s.
- const l = this.slices[from+2];
+ const len = this.slices[from+2];
// More than 3 #s is likely to be a comment in a hosts file.
- if ( l > 3 ) { return; }
- if ( l !== 1 ) {
+ if ( len > 3 ) { return; }
+ if ( len !== 1 ) {
// If a space immediately follows 2 #s, assume a comment.
- if ( l === 2 ) {
+ if ( len === 2 ) {
if ( from+3 === end || hasBits(this.slices[from+3], BITSpace) ) {
return;
}
- } else /* l === 3 */ {
+ } else /* len === 3 */ {
this.splitSlot(from, 2);
end = this.rightSpaceSpan.i;
}
- this.optionsSpan.i = this.leftSpaceSpan.i + this.leftSpaceSpan.l;
- this.optionsSpan.l = from - this.optionsSpan.i;
+ this.optionsSpan.i = this.leftSpaceSpan.i + this.leftSpaceSpan.len;
+ this.optionsSpan.len = from - this.optionsSpan.i;
this.optionsAnchorSpan.i = from;
- this.optionsAnchorSpan.l = 3;
+ this.optionsAnchorSpan.len = 3;
this.patternSpan.i = from + 3;
- this.patternSpan.l = this.rightSpaceSpan.i - this.patternSpan.i;
+ this.patternSpan.len = this.rightSpaceSpan.i - this.patternSpan.i;
this.category = CATStaticExtFilter;
this.analyzeExtPattern();
return;
@@ -257,12 +257,12 @@ const Parser = class {
this.splitSlot(to, 1);
}
to += 3;
- this.optionsSpan.i = this.leftSpaceSpan.i + this.leftSpaceSpan.l;
- this.optionsSpan.l = from - this.optionsSpan.i;
+ this.optionsSpan.i = this.leftSpaceSpan.i + this.leftSpaceSpan.len;
+ this.optionsSpan.len = from - this.optionsSpan.i;
this.optionsAnchorSpan.i = from;
- this.optionsAnchorSpan.l = to - this.optionsAnchorSpan.i;
+ this.optionsAnchorSpan.len = to - this.optionsAnchorSpan.i;
this.patternSpan.i = to;
- this.patternSpan.l = this.rightSpaceSpan.i - to;
+ this.patternSpan.len = this.rightSpaceSpan.i - to;
this.flavorBits = flavorBits;
this.category = CATStaticExtFilter;
this.analyzeExtPattern();
@@ -305,11 +305,11 @@ const Parser = class {
}
}
- // Use in syntax highlighting contexts
+ // Use in syntax highlighting contexts
analyzeExtExtra() {
if ( this.hasOptions() ) {
- const { i, l } = this.optionsSpan;
- this.analyzeDomainList(i, i + l, BITComma, 0b11);
+ const { i, len } = this.optionsSpan;
+ this.analyzeDomainList(i, i + len, BITComma, 0b11);
}
if ( hasBits(this.flavorBits, BITFlavorUnsupported) ) {
this.markSpan(this.patternSpan, BITError);
@@ -341,19 +341,19 @@ const Parser = class {
let islice = this.leftSpaceSpan.i;
// Assume no exception
- this.exceptionSpan.i = this.leftSpaceSpan.l;
+ this.exceptionSpan.i = this.leftSpaceSpan.len;
// Exception?
if (
islice < this.commentSpan.i &&
hasBits(this.slices[islice], BITAt)
) {
- const l = this.slices[islice+2];
+ const len = this.slices[islice+2];
// @@@*, ... => @@, @*, ...
- if ( l >= 2 ) {
- if ( l > 2 ) {
+ if ( len >= 2 ) {
+ if ( len > 2 ) {
this.splitSlot(islice, 2);
}
- this.exceptionSpan.l = 3;
+ this.exceptionSpan.len = 3;
islice += 3;
this.flavorBits |= BITFlavorException;
}
@@ -364,17 +364,17 @@ const Parser = class {
// Assume all is part of pattern
this.patternSpan.i = islice;
- this.patternSpan.l = this.optionsAnchorSpan.i - islice;
+ this.patternSpan.len = this.optionsAnchorSpan.i - islice;
let patternStartIsRegex =
islice < this.optionsAnchorSpan.i &&
hasBits(this.slices[islice], BITSlash);
let patternIsRegex = patternStartIsRegex;
if ( patternStartIsRegex ) {
- const { i, l } = this.patternSpan;
+ const { i, len } = this.patternSpan;
patternIsRegex = (
- l === 3 && this.slices[i+2] > 2 ||
- l > 3 && hasBits(this.slices[i+l-3], BITSlash)
+ len === 3 && this.slices[i+2] > 2 ||
+ len > 3 && hasBits(this.slices[i+len-3], BITSlash)
);
}
@@ -390,8 +390,8 @@ const Parser = class {
optionsBits |= bits;
}
if ( i >= islice ) {
- const l = this.slices[i+2];
- if ( l > 1 ) {
+ const len = this.slices[i+2];
+ if ( len > 1 ) {
// https://github.com/gorhill/uBlock/issues/952
// AdGuard-specific `$$` filters => unsupported.
if ( this.findFirstOdd(0, BITHostname | BITComma | BITAsterisk) === i ) {
@@ -400,22 +400,22 @@ const Parser = class {
this.markSlices(i, i+3, BITError);
}
} else {
- this.splitSlot(i, l - 1);
+ this.splitSlot(i, len - 1);
i += 3;
}
}
- this.patternSpan.l = i - this.patternSpan.i;
+ this.patternSpan.len = i - this.patternSpan.i;
this.optionsAnchorSpan.i = i;
- this.optionsAnchorSpan.l = 3;
+ this.optionsAnchorSpan.len = 3;
i += 3;
this.optionsSpan.i = i;
- this.optionsSpan.l = this.commentSpan.i - i;
+ this.optionsSpan.len = this.commentSpan.i - i;
this.optionsBits = optionsBits;
if ( patternStartIsRegex ) {
- const { i, l } = this.patternSpan;
+ const { i, len } = this.patternSpan;
patternIsRegex = (
- l === 3 && this.slices[i+2] > 2 ||
- l > 3 && hasBits(this.slices[i+l-3], BITSlash)
+ len === 3 && this.slices[i+2] > 2 ||
+ len > 3 && hasBits(this.slices[i+len-3], BITSlash)
);
}
}
@@ -437,19 +437,19 @@ const Parser = class {
// `|`: anchor to start of URL
// `||`: anchor to left of a hostname label
if (
- this.patternSpan.l !== 0 &&
+ this.patternSpan.len !== 0 &&
hasBits(this.slices[this.patternSpan.i], BITPipe)
) {
- this.patternLeftAnchorSpan.l = 3;
- const l = this.slices[this.patternSpan.i+2];
+ this.patternLeftAnchorSpan.len = 3;
+ const len = this.slices[this.patternSpan.i+2];
// |||*, ... => ||, |*, ...
- if ( l > 2 ) {
+ if ( len > 2 ) {
this.splitSlot(this.patternSpan.i, 2);
} else {
- this.patternSpan.l -= 3;
+ this.patternSpan.len -= 3;
}
this.patternSpan.i += 3;
- this.flavorBits |= l === 1
+ this.flavorBits |= len === 1
? BITFlavorNetLeftURLAnchor
: BITFlavorNetLeftHnAnchor;
}
@@ -459,21 +459,21 @@ const Parser = class {
// fulfilled:
// the pattern is hostname-anchored on the left
// the pattern is made only of hostname characters
- if ( this.patternSpan.l !== 0 ) {
- const lastPatternSlice = this.patternSpan.l > 3
+ if ( this.patternSpan.len !== 0 ) {
+ const lastPatternSlice = this.patternSpan.len > 3
? this.patternRightAnchorSpan.i - 3
: this.patternSpan.i;
const bits = this.slices[lastPatternSlice];
if ( (bits & BITPipe) !== 0 ) {
this.patternRightAnchorSpan.i = lastPatternSlice;
- this.patternRightAnchorSpan.l = 3;
- const l = this.slices[this.patternRightAnchorSpan.i+2];
+ this.patternRightAnchorSpan.len = 3;
+ const len = this.slices[this.patternRightAnchorSpan.i+2];
// ..., ||* => ..., |*, |
- if ( l > 1 ) {
- this.splitSlot(this.patternRightAnchorSpan.i, l - 1);
+ if ( len > 1 ) {
+ this.splitSlot(this.patternRightAnchorSpan.i, len - 1);
this.patternRightAnchorSpan.i += 3;
} else {
- this.patternSpan.l -= 3;
+ this.patternSpan.len -= 3;
}
this.flavorBits |= BITFlavorNetRightURLAnchor;
} else if (
@@ -487,8 +487,8 @@ const Parser = class {
) === lastPatternSlice
) {
this.patternRightAnchorSpan.i = lastPatternSlice;
- this.patternRightAnchorSpan.l = 3;
- this.patternSpan.l -= 3;
+ this.patternRightAnchorSpan.len = 3;
+ this.patternSpan.len -= 3;
this.flavorBits |= BITFlavorNetRightHnAnchor;
}
}
@@ -502,8 +502,8 @@ const Parser = class {
// if a pattern contains space characters, the pattern will be only
// the part following the last space occurrence.
{
- const { i, l } = this.patternSpan;
- let j = l;
+ const { i, len } = this.patternSpan;
+ let j = len;
for (;;) {
if ( j === 0 ) { break; }
j -= 3;
@@ -513,7 +513,7 @@ const Parser = class {
}
if ( j !== 0 ) {
this.patternSpan.i += j + 3;
- this.patternSpan.l -= j + 3;
+ this.patternSpan.len -= j + 3;
if ( this.reIsLocalhostRedirect.test(this.getNetPattern()) ) {
this.flavorBits |= BITFlavorIgnore;
}
@@ -539,41 +539,41 @@ const Parser = class {
// https://github.com/gorhill/uBlock/issues/3034
// We can remove anchoring if we need to match all at the end.
{
- let { i, l } = this.patternSpan;
+ let { i, len } = this.patternSpan;
// Pointless leading wildcard
if (
- l > 3 &&
+ len > 3 &&
hasBits(this.slices[i], BITAsterisk) &&
hasNoBits(this.slices[i+3], BITPatternToken)
) {
this.slices[i] |= BITIgnore;
- i += 3; l -= 3;
+ i += 3; len -= 3;
this.patternSpan.i = i;
- this.patternSpan.l = l;
+ this.patternSpan.len = len;
// We can ignore left-hand pattern anchor
- if ( this.patternLeftAnchorSpan.l !== 0 ) {
+ if ( this.patternLeftAnchorSpan.len !== 0 ) {
this.slices[this.patternLeftAnchorSpan.i] |= BITIgnore;
this.flavorBits &= ~BITFlavorNetLeftAnchor;
}
}
// Pointless trailing wildcard
if (
- l > 3 &&
- hasBits(this.slices[i+l-3], BITAsterisk) &&
- hasNoBits(this.slices[i+l-6], BITPatternToken)
+ len > 3 &&
+ hasBits(this.slices[i+len-3], BITAsterisk) &&
+ hasNoBits(this.slices[i+len-6], BITPatternToken)
) {
// Ignore only if the pattern would not end up looking like
// a regex.
if (
hasNoBits(this.slices[i], BITSlash) ||
- hasNoBits(this.slices[i+l-6], BITSlash)
+ hasNoBits(this.slices[i+len-6], BITSlash)
) {
- this.slices[i+l-3] |= BITIgnore;
+ this.slices[i+len-3] |= BITIgnore;
}
- l -= 3;
- this.patternSpan.l = l;
+ len -= 3;
+ this.patternSpan.len = len;
// We can ignore right-hand pattern anchor
- if ( this.patternRightAnchorSpan.l !== 0 ) {
+ if ( this.patternRightAnchorSpan.len !== 0 ) {
this.slices[this.patternRightAnchorSpan.i] |= BITIgnore;
this.flavorBits &= ~BITFlavorNetRightAnchor;
}
@@ -581,8 +581,8 @@ const Parser = class {
// Pointless left-hand pattern anchoring
if (
(
- l === 0 ||
- l !== 0 && hasBits(this.slices[i], BITAsterisk)
+ len === 0 ||
+ len !== 0 && hasBits(this.slices[i], BITAsterisk)
) &&
hasBits(this.flavorBits, BITFlavorNetLeftAnchor)
) {
@@ -592,8 +592,8 @@ const Parser = class {
// Pointless right-hand pattern anchoring
if (
(
- l === 0 ||
- l !== 0 && hasBits(this.slices[i+l-3], BITAsterisk)
+ len === 0 ||
+ len !== 0 && hasBits(this.slices[i+len-3], BITAsterisk)
) &&
hasBits(this.flavorBits, BITFlavorNetRightAnchor)
) {
@@ -747,9 +747,9 @@ const Parser = class {
ptr += 3;
// Trim left
if ( (slices[0] & BITSpace) !== 0 ) {
- this.leftSpaceSpan.l = 3;
+ this.leftSpaceSpan.len = 3;
} else {
- this.leftSpaceSpan.l = 0;
+ this.leftSpaceSpan.len = 0;
}
// Trim right
const lastSlice = this.eolSpan.i - 3;
@@ -758,25 +758,25 @@ const Parser = class {
(slices[lastSlice] & BITSpace) !== 0
) {
this.rightSpaceSpan.i = lastSlice;
- this.rightSpaceSpan.l = 3;
+ this.rightSpaceSpan.len = 3;
} else {
this.rightSpaceSpan.i = this.eolSpan.i;
- this.rightSpaceSpan.l = 0;
+ this.rightSpaceSpan.len = 0;
}
// Quit cleanly
this.sliceWritePtr = ptr;
this.allBits = allBits;
}
- splitSlot(slot, l) {
+ splitSlot(slot, len) {
this.sliceWritePtr += 3;
if ( this.sliceWritePtr > this.slices.length ) {
this.slices.push(0, 0, 0);
}
this.slices.copyWithin(slot + 3, slot, this.sliceWritePtr - 3);
- this.slices[slot+3+1] = this.slices[slot+1] + l;
- this.slices[slot+3+2] = this.slices[slot+2] - l;
- this.slices[slot+2] = l;
+ this.slices[slot+3+1] = this.slices[slot+1] + len;
+ this.slices[slot+3+2] = this.slices[slot+2] - len;
+ this.slices[slot+2] = len;
for ( const span of this.spans ) {
if ( span.i > slot ) {
span.i += 3;
@@ -792,8 +792,8 @@ const Parser = class {
}
markSpan(span, bits) {
- const { i, l } = span;
- this.markSlices(i, i + l, bits);
+ const { i, len } = span;
+ this.markSlices(i, i + len, bits);
}
unmarkSlices(beg, end, bits) {
@@ -847,9 +847,9 @@ const Parser = class {
}
strFromSpan(span) {
- if ( span.l === 0 ) { return ''; }
+ if ( span.len === 0 ) { return ''; }
const beg = span.i;
- return this.strFromSlices(beg, beg + span.l - 3);
+ return this.strFromSlices(beg, beg + span.len - 3);
}
isBlank() {
@@ -857,25 +857,25 @@ const Parser = class {
}
hasOptions() {
- return this.optionsSpan.l !== 0;
+ return this.optionsSpan.len !== 0;
}
getPattern() {
if ( this.pattern !== '' ) { return this.pattern; }
- const { i, l } = this.patternSpan;
- if ( l === 0 ) { return ''; }
+ const { i, len } = this.patternSpan;
+ if ( len === 0 ) { return ''; }
let beg = this.slices[i+1];
- let end = this.slices[i+l+1];
+ let end = this.slices[i+len+1];
this.pattern = this.raw.slice(beg, end);
return this.pattern;
}
getNetPattern() {
if ( this.pattern !== '' ) { return this.pattern; }
- const { i, l } = this.patternSpan;
- if ( l === 0 ) { return ''; }
+ const { i, len } = this.patternSpan;
+ if ( len === 0 ) { return ''; }
let beg = this.slices[i+1];
- let end = this.slices[i+l+1];
+ let end = this.slices[i+len+1];
if ( hasBits(this.flavorBits, BITFlavorNetRegex) ) {
beg += 1; end -= 1;
}
@@ -888,15 +888,15 @@ const Parser = class {
// - Single character other than `*` wildcard
patternIsDubious() {
return this.patternBits !== BITAsterisk &&
- this.optionsSpan.l === 0 &&
- this.patternSpan.l === 3 &&
+ this.optionsSpan.len === 0 &&
+ this.patternSpan.len === 3 &&
this.slices[this.patternSpan.i+2] === 1;
}
patternIsMatchAll() {
- const { l } = this.patternSpan;
- return l === 0 ||
- l === 3 && hasBits(this.patternBits, BITAsterisk);
+ const { len } = this.patternSpan;
+ return len === 0 ||
+ len === 3 && hasBits(this.patternBits, BITAsterisk);
}
patternIsPlainHostname() {
@@ -908,9 +908,9 @@ const Parser = class {
) {
return false;
}
- const { i, l } = this.patternSpan;
+ const { i, len } = this.patternSpan;
return hasBits(this.slices[i], BITAlphaNum) &&
- hasBits(this.slices[i+l-3], BITAlphaNum);
+ hasBits(this.slices[i+len-3], BITAlphaNum);
}
patternIsLeftHostnameAnchored() {
@@ -954,17 +954,17 @@ const Parser = class {
if ( hasUpper === false && this.pattern !== '' ) {
return this.pattern;
}
- const { i, l } = this.patternSpan;
- if ( l === 0 ) { return ''; }
+ const { i, len } = this.patternSpan;
+ if ( len === 0 ) { return ''; }
const beg = this.slices[i+1];
- const end = this.slices[i+l+1];
+ const end = this.slices[i+len+1];
this.pattern = this.pattern || this.raw.slice(beg, end);
if ( hasUpper === false ) { return this.pattern; }
this.pattern = this.pattern.toLowerCase();
this.raw = this.raw.slice(0, beg) +
this.pattern +
this.raw.slice(end);
- this.unmarkSlices(i, i+l, BITUppercase);
+ this.unmarkSlices(i, i + len, BITUppercase);
this.patternBits &= ~BITUppercase;
return this.pattern;
}
@@ -977,16 +977,16 @@ const Parser = class {
if ( hasBits(this.patternBits, BITAsterisk) === false ) {
return false;
}
- const { i, l } = this.patternSpan;
- return l !== 0 && hasBits(this.slices[i], BITAsterisk);
+ const { i, len } = this.patternSpan;
+ return len !== 0 && hasBits(this.slices[i], BITAsterisk);
}
patternHasTrailingWildcard() {
if ( hasBits(this.patternBits, BITAsterisk) === false ) {
return false;
}
- const { i, l } = this.patternSpan;
- return l !== 0 && hasBits(this.slices[i+l-1], BITAsterisk);
+ const { i, len } = this.patternSpan;
+ return len !== 0 && hasBits(this.slices[i+len-1], BITAsterisk);
}
optionHasUnicode() {
@@ -1008,8 +1008,8 @@ const Parser = class {
return [];
}
- setMaxTokenLength(l) {
- this.maxTokenLength = l;
+ setMaxTokenLength(len) {
+ this.maxTokenLength = len;
}
hasUnicode() {
@@ -1025,8 +1025,8 @@ const Parser = class {
toPunycode() {
if ( this.patternHasUnicode() === false ) { return true; }
- const { i, l } = this.patternSpan;
- if ( l === 0 ) { return true; }
+ const { i, len } = this.patternSpan;
+ if ( len === 0 ) { return true; }
let pattern = this.getNetPattern();
const match = this.reHostname.exec(this.pattern);
if ( match === null ) { return; }
@@ -1038,7 +1038,7 @@ const Parser = class {
const punycoded = this.punycoder.hostname.replace(/__asterisk__/g, '*');
pattern = punycoded + this.pattern.slice(match.index + match[0].length);
const beg = this.slices[i+1];
- const end = this.slices[i+l+1];
+ const end = this.slices[i+len+1];
const raw = this.raw.slice(0, beg) + pattern + this.raw.slice(end);
this.analyze(raw);
return true;
@@ -1936,7 +1936,7 @@ const Span = class {
this.reset();
}
reset() {
- this.i = this.l = 0;
+ this.i = this.len = 0;
}
};
@@ -1963,7 +1963,7 @@ const NetOptionsIterator = class {
}
init() {
this.readPtr = this.writePtr = 0;
- this.done = this.parser.optionsSpan.l === 0;
+ this.done = this.parser.optionsSpan.len === 0;
if ( this.done ) {
this.value = undefined;
return this;
@@ -1981,7 +1981,7 @@ const NetOptionsIterator = class {
// At index 0 is the option descriptor.
// At indices 1-5 is a slice index.
const lopts = this.parser.optionsSpan.i;
- const ropts = lopts + this.parser.optionsSpan.l;
+ const ropts = lopts + this.parser.optionsSpan.len;
const slices = this.parser.slices;
const optSlices = this.optSlices;
let typeCount = 0;
@@ -2206,12 +2206,12 @@ const PatternTokenIterator = class {
this.done = true;
}
[Symbol.iterator]() {
- const { i, l } = this.parser.patternSpan;
- if ( l === 0 ) {
+ const { i, len } = this.parser.patternSpan;
+ if ( len === 0 ) {
return this.end();
}
this.l = i;
- this.r = i + l;
+ this.r = i + len;
this.i = i;
this.done = false;
this.value = { token: '', pos: 0 };
@@ -2269,14 +2269,14 @@ const ExtOptionsIterator = class {
this.done = true;
}
[Symbol.iterator]() {
- const { i, l } = this.parser.optionsSpan;
- if ( l === 0 ) {
+ const { i, len } = this.parser.optionsSpan;
+ if ( len === 0 ) {
this.l = this.r = 0;
this.done = true;
this.value = undefined;
} else {
this.l = i;
- this.r = i + l;
+ this.r = i + len;
this.done = false;
this.value = { hn: undefined, not: false, bad: false };
}
diff --git a/src/lib/codemirror/addon/edit/closebrackets.js b/src/lib/codemirror/addon/edit/closebrackets.js
new file mode 100644
index 000000000..4415c3938
--- /dev/null
+++ b/src/lib/codemirror/addon/edit/closebrackets.js
@@ -0,0 +1,191 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ var defaults = {
+ pairs: "()[]{}''\"\"",
+ closeBefore: ")]}'\":;>",
+ triples: "",
+ explode: "[]{}"
+ };
+
+ var Pos = CodeMirror.Pos;
+
+ CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
+ if (old && old != CodeMirror.Init) {
+ cm.removeKeyMap(keyMap);
+ cm.state.closeBrackets = null;
+ }
+ if (val) {
+ ensureBound(getOption(val, "pairs"))
+ cm.state.closeBrackets = val;
+ cm.addKeyMap(keyMap);
+ }
+ });
+
+ function getOption(conf, name) {
+ if (name == "pairs" && typeof conf == "string") return conf;
+ if (typeof conf == "object" && conf[name] != null) return conf[name];
+ return defaults[name];
+ }
+
+ var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
+ function ensureBound(chars) {
+ for (var i = 0; i < chars.length; i++) {
+ var ch = chars.charAt(i), key = "'" + ch + "'"
+ if (!keyMap[key]) keyMap[key] = handler(ch)
+ }
+ }
+ ensureBound(defaults.pairs + "`")
+
+ function handler(ch) {
+ return function(cm) { return handleChar(cm, ch); };
+ }
+
+ function getConfig(cm) {
+ var deflt = cm.state.closeBrackets;
+ if (!deflt || deflt.override) return deflt;
+ var mode = cm.getModeAt(cm.getCursor());
+ return mode.closeBrackets || deflt;
+ }
+
+ function handleBackspace(cm) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var cur = ranges[i].head;
+ cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
+ }
+ }
+
+ function handleEnter(cm) {
+ var conf = getConfig(cm);
+ var explode = conf && getOption(conf, "explode");
+ if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ cm.operation(function() {
+ var linesep = cm.lineSeparator() || "\n";
+ cm.replaceSelection(linesep + linesep, null);
+ cm.execCommand("goCharLeft");
+ ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var line = ranges[i].head.line;
+ cm.indentLine(line, null, true);
+ cm.indentLine(line + 1, null, true);
+ }
+ });
+ }
+
+ function contractSelection(sel) {
+ var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
+ return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
+ head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
+ }
+
+ function handleChar(cm, ch) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var pos = pairs.indexOf(ch);
+ if (pos == -1) return CodeMirror.Pass;
+
+ var closeBefore = getOption(conf,"closeBefore");
+
+ var triples = getOption(conf, "triples");
+
+ var identical = pairs.charAt(pos + 1) == ch;
+ var ranges = cm.listSelections();
+ var opening = pos % 2 == 0;
+
+ var type;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], cur = range.head, curType;
+ var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
+ if (opening && !range.empty()) {
+ curType = "surround";
+ } else if ((identical || !opening) && next == ch) {
+ if (identical && stringStartsAfter(cm, cur))
+ curType = "both";
+ else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
+ curType = "skipThree";
+ else
+ curType = "skip";
+ } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
+ cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
+ if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
+ curType = "addFour";
+ } else if (identical) {
+ var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
+ if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
+ else return CodeMirror.Pass;
+ } else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
+ curType = "both";
+ } else {
+ return CodeMirror.Pass;
+ }
+ if (!type) type = curType;
+ else if (type != curType) return CodeMirror.Pass;
+ }
+
+ var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
+ var right = pos % 2 ? ch : pairs.charAt(pos + 1);
+ cm.operation(function() {
+ if (type == "skip") {
+ cm.execCommand("goCharRight");
+ } else if (type == "skipThree") {
+ for (var i = 0; i < 3; i++)
+ cm.execCommand("goCharRight");
+ } else if (type == "surround") {
+ var sels = cm.getSelections();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = left + sels[i] + right;
+ cm.replaceSelections(sels, "around");
+ sels = cm.listSelections().slice();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = contractSelection(sels[i]);
+ cm.setSelections(sels);
+ } else if (type == "both") {
+ cm.replaceSelection(left + right, null);
+ cm.triggerElectric(left + right);
+ cm.execCommand("goCharLeft");
+ } else if (type == "addFour") {
+ cm.replaceSelection(left + left + left + left, "before");
+ cm.execCommand("goCharRight");
+ }
+ });
+ }
+
+ function charsAround(cm, pos) {
+ var str = cm.getRange(Pos(pos.line, pos.ch - 1),
+ Pos(pos.line, pos.ch + 1));
+ return str.length == 2 ? str : null;
+ }
+
+ function stringStartsAfter(cm, pos) {
+ var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
+ return /\bstring/.test(token.type) && token.start == pos.ch &&
+ (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
+ }
+});
diff --git a/src/lib/codemirror/addon/edit/matchbrackets.js b/src/lib/codemirror/addon/edit/matchbrackets.js
new file mode 100644
index 000000000..2a147282c
--- /dev/null
+++ b/src/lib/codemirror/addon/edit/matchbrackets.js
@@ -0,0 +1,150 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
+ (document.documentMode == null || document.documentMode < 8);
+
+ var Pos = CodeMirror.Pos;
+
+ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<"};
+
+ function bracketRegex(config) {
+ return config && config.bracketRegex || /[(){}[\]]/
+ }
+
+ function findMatchingBracket(cm, where, config) {
+ var line = cm.getLineHandle(where.line), pos = where.ch - 1;
+ var afterCursor = config && config.afterCursor
+ if (afterCursor == null)
+ afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)
+ var re = bracketRegex(config)
+
+ // A cursor is defined as between two characters, but in in vim command mode
+ // (i.e. not insert mode), the cursor is visually represented as a
+ // highlighted box on top of the 2nd character. Otherwise, we allow matches
+ // from before or after the cursor.
+ var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) ||
+ re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)];
+ if (!match) return null;
+ var dir = match.charAt(1) == ">" ? 1 : -1;
+ if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;
+ var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
+
+ var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
+ if (found == null) return null;
+ return {from: Pos(where.line, pos), to: found && found.pos,
+ match: found && found.ch == match.charAt(0), forward: dir > 0};
+ }
+
+ // bracketRegex is used to specify which type of bracket to scan
+ // should be a regexp, e.g. /[[\]]/
+ //
+ // Note: If "where" is on an open bracket, then this bracket is ignored.
+ //
+ // Returns false when no bracket was found, null when it reached
+ // maxScanLines and gave up
+ function scanForBracket(cm, where, dir, style, config) {
+ var maxScanLen = (config && config.maxScanLineLength) || 10000;
+ var maxScanLines = (config && config.maxScanLines) || 1000;
+
+ var stack = [];
+ var re = bracketRegex(config)
+ var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
+ : Math.max(cm.firstLine() - 1, where.line - maxScanLines);
+ for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
+ var line = cm.getLine(lineNo);
+ if (!line) continue;
+ var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
+ if (line.length > maxScanLen) continue;
+ if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
+ for (; pos != end; pos += dir) {
+ var ch = line.charAt(pos);
+ if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
+ var match = matching[ch];
+ if (match && (match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
+ else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
+ else stack.pop();
+ }
+ }
+ }
+ return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
+ }
+
+ function matchBrackets(cm, autoclear, config) {
+ // Disable brace matching in long lines, since it'll cause hugely slow updates
+ var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
+ var marks = [], ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);
+ if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
+ var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
+ marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
+ if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
+ marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
+ }
+ }
+
+ if (marks.length) {
+ // Kludge to work around the IE bug from issue #1193, where text
+ // input stops going to the textare whever this fires.
+ if (ie_lt8 && cm.state.focused) cm.focus();
+
+ var clear = function() {
+ cm.operation(function() {
+ for (var i = 0; i < marks.length; i++) marks[i].clear();
+ });
+ };
+ if (autoclear) setTimeout(clear, 800);
+ else return clear;
+ }
+ }
+
+ function doMatchBrackets(cm) {
+ cm.operation(function() {
+ if (cm.state.matchBrackets.currentlyHighlighted) {
+ cm.state.matchBrackets.currentlyHighlighted();
+ cm.state.matchBrackets.currentlyHighlighted = null;
+ }
+ cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
+ });
+ }
+
+ CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
+ if (old && old != CodeMirror.Init) {
+ cm.off("cursorActivity", doMatchBrackets);
+ if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) {
+ cm.state.matchBrackets.currentlyHighlighted();
+ cm.state.matchBrackets.currentlyHighlighted = null;
+ }
+ }
+ if (val) {
+ cm.state.matchBrackets = typeof val == "object" ? val : {};
+ cm.on("cursorActivity", doMatchBrackets);
+ }
+ });
+
+ CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
+ CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){
+ // Backwards-compatibility kludge
+ if (oldConfig || typeof config == "boolean") {
+ if (!oldConfig) {
+ config = config ? {strict: true} : null
+ } else {
+ oldConfig.strict = config
+ config = oldConfig
+ }
+ }
+ return findMatchingBracket(this, pos, config)
+ });
+ CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
+ return scanForBracket(this, pos, dir, style, config);
+ });
+});