Minor code review of static filtering parser

Rename `l` property to `len`, to avoid ambiguity as
`l` could mean _left_ or _length_. Typically `l` is
to be used for _left_ (whereas `r` is to be used for
_right_).

Additionally, add CodeMirror's bracket-matching and
bracket auto-closing to _My filters_ pane and and
bracket-matching to asset viewer page.
This commit is contained in:
Raymond Hill 2020-06-14 12:05:42 -04:00
parent 2523959f20
commit 6d8b310d94
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
9 changed files with 555 additions and 161 deletions

View file

@ -38,6 +38,8 @@
<script src="lib/codemirror/lib/codemirror.js"></script>
<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/scroll/annotatescrollbar.js"></script>
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="lib/codemirror/addon/search/searchcursor.js"></script>

View file

@ -30,6 +30,7 @@ body {
<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/scroll/annotatescrollbar.js"></script>
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="lib/codemirror/addon/search/searchcursor.js"></script>

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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';
}

View file

@ -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 };
}

View file

@ -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)))
}
});

View file

@ -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);
});
});