mirror of https://github.com/gorhill/uBlock.git
Add CoreMirror's code-folding ability to list editor/viewer
Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1134 CodeMirror's code folding reference: - https://codemirror.net/doc/manual.html#addon_foldcode This commit adds support for code-folding to the filter list editor/viewer. The following blocks of code are foldable by clicking the corresponding marker in the gutter: - !#if/#endif blocks - !#include blocks Addtionally, the following changes: - The `!#include` line is now preserved when importing a sublist - The `!#if` directives will be syntax-colored according to whether they evaluate to true or false on the current platform - Double-clicking on a foldable line in the gutter will select the content of the foldable block - Minor visual improvement to matching brackets
This commit is contained in:
parent
f955d502c3
commit
e44a568278
|
@ -6,6 +6,7 @@
|
||||||
<title>uBlock — Your filters</title>
|
<title>uBlock — Your filters</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
|
<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/hint/show-hint.css">
|
||||||
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
|
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
|
||||||
|
|
||||||
|
@ -42,6 +43,8 @@
|
||||||
<script src="lib/codemirror/addon/display/panel.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/closebrackets.js"></script>
|
||||||
<script src="lib/codemirror/addon/edit/matchbrackets.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/hint/show-hint.js"></script>
|
||||||
<script src="lib/codemirror/addon/scroll/annotatescrollbar.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/matchesonscrollbar.js"></script>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title data-i18n="assetViewerPageName"></title>
|
<title data-i18n="assetViewerPageName"></title>
|
||||||
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
|
<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="lib/codemirror/addon/search/matchesonscrollbar.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/themes/default.css">
|
<link rel="stylesheet" type="text/css" href="css/themes/default.css">
|
||||||
<link rel="stylesheet" href="css/common.css">
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
@ -31,6 +32,8 @@ body {
|
||||||
<script src="lib/codemirror/lib/codemirror.js"></script>
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
<script src="lib/codemirror/addon/display/panel.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/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/scroll/annotatescrollbar.js"></script>
|
||||||
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
|
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||||
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||||
|
|
|
@ -52,6 +52,7 @@ div.CodeMirror span.CodeMirror-matchingbracket {
|
||||||
color: unset;
|
color: unset;
|
||||||
}
|
}
|
||||||
.CodeMirror-matchingbracket {
|
.CodeMirror-matchingbracket {
|
||||||
|
background-color: #afa;
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ const cmEditor = new CodeMirror(document.getElementById('userFilters'), {
|
||||||
'Ctrl-Space': 'autocomplete',
|
'Ctrl-Space': 'autocomplete',
|
||||||
'Tab': 'toggleComment',
|
'Tab': 'toggleComment',
|
||||||
},
|
},
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
const cmEditor = new CodeMirror(document.getElementById('content'), {
|
const cmEditor = new CodeMirror(document.getElementById('content'), {
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
|
|
|
@ -276,7 +276,7 @@ api.fetchFilterList = async function(mainlistURL) {
|
||||||
if ( sublistURLs.has(subURL) ) { continue; }
|
if ( sublistURLs.has(subURL) ) { continue; }
|
||||||
sublistURLs.add(subURL);
|
sublistURLs.add(subURL);
|
||||||
out.push(
|
out.push(
|
||||||
slice.slice(lastIndex, match.index),
|
slice.slice(lastIndex, match.index + match[0].length),
|
||||||
`! >>>>>>>> ${subURL}`,
|
`! >>>>>>>> ${subURL}`,
|
||||||
api.fetchText(subURL),
|
api.fetchText(subURL),
|
||||||
`! <<<<<<<< ${subURL}`
|
`! <<<<<<<< ${subURL}`
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
const redirectNames = new Map();
|
const redirectNames = new Map();
|
||||||
const scriptletNames = new Map();
|
const scriptletNames = new Map();
|
||||||
const preparseDirectiveTokens = new Set();
|
const preparseDirectiveTokens = new Map();
|
||||||
const preparseDirectiveHints = [];
|
const preparseDirectiveHints = [];
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -44,8 +44,8 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
|
||||||
if ( StaticFilteringParser instanceof Object === false ) { return; }
|
if ( StaticFilteringParser instanceof Object === false ) { return; }
|
||||||
const parser = new StaticFilteringParser({ interactive: true });
|
const parser = new StaticFilteringParser({ interactive: true });
|
||||||
|
|
||||||
const rePreparseDirectives = /^!#(?:if|endif|include)\b/;
|
const rePreparseDirectives = /^!#(?:if|endif|include )\b/;
|
||||||
const rePreparseIfDirective = /^(!#if !?)(.+)$/;
|
const rePreparseIfDirective = /^(!#if ?)(.*)$/;
|
||||||
let parserSlot = 0;
|
let parserSlot = 0;
|
||||||
let netOptionValueMode = false;
|
let netOptionValueMode = false;
|
||||||
|
|
||||||
|
@ -60,17 +60,28 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
|
||||||
return 'variable strong';
|
return 'variable strong';
|
||||||
}
|
}
|
||||||
if ( stream.pos < match[1].length ) {
|
if ( stream.pos < match[1].length ) {
|
||||||
stream.pos = match[1].length;
|
stream.pos += match[1].length;
|
||||||
return 'variable strong';
|
return 'variable strong';
|
||||||
}
|
}
|
||||||
stream.skipToEnd();
|
stream.skipToEnd();
|
||||||
if (
|
if ( match[1].endsWith(' ') === false ) {
|
||||||
preparseDirectiveTokens.size === 0 ||
|
return 'error strong';
|
||||||
preparseDirectiveTokens.has(match[2].trim())
|
|
||||||
) {
|
|
||||||
return 'variable strong';
|
|
||||||
}
|
}
|
||||||
return 'error strong';
|
if ( preparseDirectiveTokens.size === 0 ) {
|
||||||
|
return 'positive strong';
|
||||||
|
}
|
||||||
|
let token = match[2];
|
||||||
|
const not = token.startsWith('!');
|
||||||
|
if ( not ) {
|
||||||
|
token = token.slice(1);
|
||||||
|
}
|
||||||
|
if ( preparseDirectiveTokens.has(token) === false ) {
|
||||||
|
return 'error strong';
|
||||||
|
}
|
||||||
|
if ( not !== preparseDirectiveTokens.get(token) ) {
|
||||||
|
return 'positive strong';
|
||||||
|
}
|
||||||
|
return 'negative strong';
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorExtHTMLPatternSpan = function(stream) {
|
const colorExtHTMLPatternSpan = function(stream) {
|
||||||
|
@ -289,8 +300,8 @@ CodeMirror.defineMode('ubo-static-filtering', function() {
|
||||||
scriptletNames.set(name.slice(0, -3), displayText);
|
scriptletNames.set(name.slice(0, -3), displayText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
details.preparseDirectiveTokens.forEach(a => {
|
details.preparseDirectiveTokens.forEach(([ a, b ]) => {
|
||||||
preparseDirectiveTokens.add(a);
|
preparseDirectiveTokens.set(a, b);
|
||||||
});
|
});
|
||||||
preparseDirectiveHints.push(...details.preparseDirectiveHints);
|
preparseDirectiveHints.push(...details.preparseDirectiveHints);
|
||||||
initHints();
|
initHints();
|
||||||
|
@ -471,6 +482,63 @@ const initHints = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => {
|
||||||
|
|
||||||
|
const foldIfEndif = function(startLineNo, startLine, cm) {
|
||||||
|
const lastLineNo = cm.lastLine();
|
||||||
|
let endLineNo = startLineNo;
|
||||||
|
let depth = 1;
|
||||||
|
while ( endLineNo < lastLineNo ) {
|
||||||
|
endLineNo += 1;
|
||||||
|
const line = cm.getLine(endLineNo);
|
||||||
|
if ( line.startsWith('!#endif') ) {
|
||||||
|
depth -= 1;
|
||||||
|
if ( depth === 0 ) {
|
||||||
|
return {
|
||||||
|
from: CodeMirror.Pos(startLineNo, startLine.length),
|
||||||
|
to: CodeMirror.Pos(endLineNo, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( line.startsWith('!#if') ) {
|
||||||
|
depth += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const foldInclude = function(startLineNo, startLine, cm) {
|
||||||
|
const lastLineNo = cm.lastLine();
|
||||||
|
let endLineNo = startLineNo + 1;
|
||||||
|
if ( endLineNo >= lastLineNo ) { return; }
|
||||||
|
if ( cm.getLine(endLineNo).startsWith('! >>>>>>>> ') === false ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while ( endLineNo < lastLineNo ) {
|
||||||
|
endLineNo += 1;
|
||||||
|
const line = cm.getLine(endLineNo);
|
||||||
|
if ( line.startsWith('! <<<<<<<< ') ) {
|
||||||
|
return {
|
||||||
|
from: CodeMirror.Pos(startLineNo, startLine.length),
|
||||||
|
to: CodeMirror.Pos(endLineNo, line.length)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return function(cm, start) {
|
||||||
|
const startLineNo = start.line;
|
||||||
|
const startLine = cm.getLine(startLineNo);
|
||||||
|
if ( startLine.startsWith('!#if') ) {
|
||||||
|
return foldIfEndif(startLineNo, startLine, cm);
|
||||||
|
}
|
||||||
|
if ( startLine.startsWith('!#include ') ) {
|
||||||
|
return foldInclude(startLineNo, startLine, cm);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})());
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
// <<<<< end of local scope
|
// <<<<< end of local scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,24 +149,37 @@ self.uBlockDashboard.patchCodeMirrorEditor = (function() {
|
||||||
let lastGutterClick = 0;
|
let lastGutterClick = 0;
|
||||||
let lastGutterLine = 0;
|
let lastGutterLine = 0;
|
||||||
|
|
||||||
const onGutterClicked = function(cm, line) {
|
const onGutterClicked = function(cm, line, gutter) {
|
||||||
|
if ( gutter !== 'CodeMirror-linenumbers' ) { return; }
|
||||||
|
grabFocusAsync(cm);
|
||||||
const delta = Date.now() - lastGutterClick;
|
const delta = Date.now() - lastGutterClick;
|
||||||
|
// Single click
|
||||||
if ( delta >= 500 || line !== lastGutterLine ) {
|
if ( delta >= 500 || line !== lastGutterLine ) {
|
||||||
cm.setSelection(
|
cm.setSelection(
|
||||||
{ line: line, ch: 0 },
|
{ line, ch: 0 },
|
||||||
{ line: line + 1, ch: 0 }
|
{ line: line + 1, ch: 0 }
|
||||||
);
|
);
|
||||||
lastGutterClick = Date.now();
|
lastGutterClick = Date.now();
|
||||||
lastGutterLine = line;
|
lastGutterLine = line;
|
||||||
} else {
|
return;
|
||||||
cm.setSelection(
|
|
||||||
{ line: 0, ch: 0 },
|
|
||||||
{ line: cm.lineCount(), ch: 0 },
|
|
||||||
{ scroll: false }
|
|
||||||
);
|
|
||||||
lastGutterClick = 0;
|
|
||||||
}
|
}
|
||||||
grabFocusAsync(cm);
|
// Double click: select fold-able block or all
|
||||||
|
let lineFrom = 0;
|
||||||
|
let lineTo = cm.lineCount();
|
||||||
|
const foldFn = cm.getHelper({ line, ch: 0 }, 'fold');
|
||||||
|
if ( foldFn instanceof Function ) {
|
||||||
|
const range = foldFn(cm, { line, ch: 0 });
|
||||||
|
if ( range !== undefined ) {
|
||||||
|
lineFrom = range.from.line;
|
||||||
|
lineTo = range.to.line + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cm.setSelection(
|
||||||
|
{ line: lineFrom, ch: 0 },
|
||||||
|
{ line: lineTo, ch: 0 },
|
||||||
|
{ scroll: false }
|
||||||
|
);
|
||||||
|
lastGutterClick = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
return function(cm) {
|
return function(cm) {
|
||||||
|
|
|
@ -862,6 +862,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
||||||
// the content string which should alternatively be parsed and discarded.
|
// the content string which should alternatively be parsed and discarded.
|
||||||
split: function(content) {
|
split: function(content) {
|
||||||
const reIf = /^!#(if|endif)\b([^\n]*)(?:[\n\r]+|$)/gm;
|
const reIf = /^!#(if|endif)\b([^\n]*)(?:[\n\r]+|$)/gm;
|
||||||
|
const soup = vAPI.webextFlavor.soup;
|
||||||
const stack = [];
|
const stack = [];
|
||||||
const shouldDiscard = ( ) => stack.some(v => v);
|
const shouldDiscard = ( ) => stack.some(v => v);
|
||||||
const parts = [ 0 ];
|
const parts = [ 0 ];
|
||||||
|
@ -878,10 +879,8 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
||||||
if ( target ) { expr = expr.slice(1); }
|
if ( target ) { expr = expr.slice(1); }
|
||||||
const token = this.tokens.get(expr);
|
const token = this.tokens.get(expr);
|
||||||
const startDiscard =
|
const startDiscard =
|
||||||
token === 'false' &&
|
token === 'false' && target === false ||
|
||||||
target === false ||
|
token !== undefined && soup.has(token) === target;
|
||||||
token !== undefined &&
|
|
||||||
vAPI.webextFlavor.soup.has(token) === target;
|
|
||||||
if ( discard === false && startDiscard ) {
|
if ( discard === false && startDiscard ) {
|
||||||
parts.push(match.index);
|
parts.push(match.index);
|
||||||
discard = true;
|
discard = true;
|
||||||
|
@ -930,7 +929,12 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
||||||
},
|
},
|
||||||
|
|
||||||
getTokens: function() {
|
getTokens: function() {
|
||||||
return Array.from(this.tokens.keys());
|
const out = new Map();
|
||||||
|
const soup = vAPI.webextFlavor.soup;
|
||||||
|
for ( const [ key, val ] of this.tokens ) {
|
||||||
|
out.set(key, val !== 'false' && soup.has(val));
|
||||||
|
}
|
||||||
|
return Array.from(out);
|
||||||
},
|
},
|
||||||
|
|
||||||
tokens: new Map([
|
tokens: new Map([
|
||||||
|
@ -947,6 +951,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
||||||
// Compatibility with other blockers
|
// Compatibility with other blockers
|
||||||
// https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#adguard-specific
|
// https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#adguard-specific
|
||||||
[ 'adguard', 'adguard' ],
|
[ 'adguard', 'adguard' ],
|
||||||
|
[ 'adguard_app_windows', 'false' ],
|
||||||
[ 'adguard_ext_chromium', 'chromium' ],
|
[ 'adguard_ext_chromium', 'chromium' ],
|
||||||
[ 'adguard_ext_edge', 'edge' ],
|
[ 'adguard_ext_edge', 'edge' ],
|
||||||
[ 'adguard_ext_firefox', 'firefox' ],
|
[ 'adguard_ext_firefox', 'firefox' ],
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
// 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) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function doFold(cm, pos, options, force) {
|
||||||
|
if (options && options.call) {
|
||||||
|
var finder = options;
|
||||||
|
options = null;
|
||||||
|
} else {
|
||||||
|
var finder = getOption(cm, options, "rangeFinder");
|
||||||
|
}
|
||||||
|
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
|
||||||
|
var minSize = getOption(cm, options, "minFoldSize");
|
||||||
|
|
||||||
|
function getRange(allowFolded) {
|
||||||
|
var range = finder(cm, pos);
|
||||||
|
if (!range || range.to.line - range.from.line < minSize) return null;
|
||||||
|
var marks = cm.findMarksAt(range.from);
|
||||||
|
for (var i = 0; i < marks.length; ++i) {
|
||||||
|
if (marks[i].__isFold && force !== "fold") {
|
||||||
|
if (!allowFolded) return null;
|
||||||
|
range.cleared = true;
|
||||||
|
marks[i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
var range = getRange(true);
|
||||||
|
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
|
||||||
|
pos = CodeMirror.Pos(pos.line - 1, 0);
|
||||||
|
range = getRange(false);
|
||||||
|
}
|
||||||
|
if (!range || range.cleared || force === "unfold") return;
|
||||||
|
|
||||||
|
var myWidget = makeWidget(cm, options);
|
||||||
|
CodeMirror.on(myWidget, "mousedown", function(e) {
|
||||||
|
myRange.clear();
|
||||||
|
CodeMirror.e_preventDefault(e);
|
||||||
|
});
|
||||||
|
var myRange = cm.markText(range.from, range.to, {
|
||||||
|
replacedWith: myWidget,
|
||||||
|
clearOnEnter: getOption(cm, options, "clearOnEnter"),
|
||||||
|
__isFold: true
|
||||||
|
});
|
||||||
|
myRange.on("clear", function(from, to) {
|
||||||
|
CodeMirror.signal(cm, "unfold", cm, from, to);
|
||||||
|
});
|
||||||
|
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeWidget(cm, options) {
|
||||||
|
var widget = getOption(cm, options, "widget");
|
||||||
|
if (typeof widget == "string") {
|
||||||
|
var text = document.createTextNode(widget);
|
||||||
|
widget = document.createElement("span");
|
||||||
|
widget.appendChild(text);
|
||||||
|
widget.className = "CodeMirror-foldmarker";
|
||||||
|
} else if (widget) {
|
||||||
|
widget = widget.cloneNode(true)
|
||||||
|
}
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clumsy backwards-compatible interface
|
||||||
|
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
|
||||||
|
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
|
||||||
|
};
|
||||||
|
|
||||||
|
// New-style interface
|
||||||
|
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
|
||||||
|
doFold(this, pos, options, force);
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("isFolded", function(pos) {
|
||||||
|
var marks = this.findMarksAt(pos);
|
||||||
|
for (var i = 0; i < marks.length; ++i)
|
||||||
|
if (marks[i].__isFold) return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.commands.toggleFold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor());
|
||||||
|
};
|
||||||
|
CodeMirror.commands.fold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor(), null, "fold");
|
||||||
|
};
|
||||||
|
CodeMirror.commands.unfold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor(), null, "unfold");
|
||||||
|
};
|
||||||
|
CodeMirror.commands.foldAll = function(cm) {
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||||
|
cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
CodeMirror.commands.unfoldAll = function(cm) {
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||||
|
cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "combine", function() {
|
||||||
|
var funcs = Array.prototype.slice.call(arguments, 0);
|
||||||
|
return function(cm, start) {
|
||||||
|
for (var i = 0; i < funcs.length; ++i) {
|
||||||
|
var found = funcs[i](cm, start);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
|
||||||
|
var helpers = cm.getHelpers(start, "fold");
|
||||||
|
for (var i = 0; i < helpers.length; i++) {
|
||||||
|
var cur = helpers[i](cm, start);
|
||||||
|
if (cur) return cur;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var defaultOptions = {
|
||||||
|
rangeFinder: CodeMirror.fold.auto,
|
||||||
|
widget: "\u2194",
|
||||||
|
minFoldSize: 0,
|
||||||
|
scanUp: false,
|
||||||
|
clearOnEnter: true
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.defineOption("foldOptions", null);
|
||||||
|
|
||||||
|
function getOption(cm, options, name) {
|
||||||
|
if (options && options[name] !== undefined)
|
||||||
|
return options[name];
|
||||||
|
var editorOptions = cm.options.foldOptions;
|
||||||
|
if (editorOptions && editorOptions[name] !== undefined)
|
||||||
|
return editorOptions[name];
|
||||||
|
return defaultOptions[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("foldOption", function(options, name) {
|
||||||
|
return getOption(this, options, name);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
.CodeMirror-foldmarker {
|
||||||
|
color: blue;
|
||||||
|
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
||||||
|
font-family: arial;
|
||||||
|
line-height: .3;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter {
|
||||||
|
width: .7em;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-open,
|
||||||
|
.CodeMirror-foldgutter-folded {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-open:after {
|
||||||
|
content: "\25BE";
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-folded:after {
|
||||||
|
content: "\25B8";
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
// 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"), require("./foldcode"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "./foldcode"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
|
||||||
|
if (old && old != CodeMirror.Init) {
|
||||||
|
cm.clearGutter(cm.state.foldGutter.options.gutter);
|
||||||
|
cm.state.foldGutter = null;
|
||||||
|
cm.off("gutterClick", onGutterClick);
|
||||||
|
cm.off("change", onChange);
|
||||||
|
cm.off("viewportChange", onViewportChange);
|
||||||
|
cm.off("fold", onFold);
|
||||||
|
cm.off("unfold", onFold);
|
||||||
|
cm.off("swapDoc", onChange);
|
||||||
|
}
|
||||||
|
if (val) {
|
||||||
|
cm.state.foldGutter = new State(parseOptions(val));
|
||||||
|
updateInViewport(cm);
|
||||||
|
cm.on("gutterClick", onGutterClick);
|
||||||
|
cm.on("change", onChange);
|
||||||
|
cm.on("viewportChange", onViewportChange);
|
||||||
|
cm.on("fold", onFold);
|
||||||
|
cm.on("unfold", onFold);
|
||||||
|
cm.on("swapDoc", onChange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
function State(options) {
|
||||||
|
this.options = options;
|
||||||
|
this.from = this.to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptions(opts) {
|
||||||
|
if (opts === true) opts = {};
|
||||||
|
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
|
||||||
|
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
|
||||||
|
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFolded(cm, line) {
|
||||||
|
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
|
||||||
|
for (var i = 0; i < marks.length; ++i)
|
||||||
|
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function marker(spec) {
|
||||||
|
if (typeof spec == "string") {
|
||||||
|
var elt = document.createElement("div");
|
||||||
|
elt.className = spec + " CodeMirror-guttermarker-subtle";
|
||||||
|
return elt;
|
||||||
|
} else {
|
||||||
|
return spec.cloneNode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFoldInfo(cm, from, to) {
|
||||||
|
var opts = cm.state.foldGutter.options, cur = from;
|
||||||
|
var minSize = cm.foldOption(opts, "minFoldSize");
|
||||||
|
var func = cm.foldOption(opts, "rangeFinder");
|
||||||
|
cm.eachLine(from, to, function(line) {
|
||||||
|
var mark = null;
|
||||||
|
if (isFolded(cm, cur)) {
|
||||||
|
mark = marker(opts.indicatorFolded);
|
||||||
|
} else {
|
||||||
|
var pos = Pos(cur, 0);
|
||||||
|
var range = func && func(cm, pos);
|
||||||
|
if (range && range.to.line - range.from.line >= minSize)
|
||||||
|
mark = marker(opts.indicatorOpen);
|
||||||
|
}
|
||||||
|
cm.setGutterMarker(line, opts.gutter, mark);
|
||||||
|
++cur;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInViewport(cm) {
|
||||||
|
var vp = cm.getViewport(), state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
cm.operation(function() {
|
||||||
|
updateFoldInfo(cm, vp.from, vp.to);
|
||||||
|
});
|
||||||
|
state.from = vp.from; state.to = vp.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGutterClick(cm, line, gutter) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
if (gutter != opts.gutter) return;
|
||||||
|
var folded = isFolded(cm, line);
|
||||||
|
if (folded) folded.clear();
|
||||||
|
else cm.foldCode(Pos(line, 0), opts.rangeFinder);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(cm) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
state.from = state.to = 0;
|
||||||
|
clearTimeout(state.changeUpdate);
|
||||||
|
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onViewportChange(cm) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
clearTimeout(state.changeUpdate);
|
||||||
|
state.changeUpdate = setTimeout(function() {
|
||||||
|
var vp = cm.getViewport();
|
||||||
|
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
||||||
|
updateInViewport(cm);
|
||||||
|
} else {
|
||||||
|
cm.operation(function() {
|
||||||
|
if (vp.from < state.from) {
|
||||||
|
updateFoldInfo(cm, vp.from, state.from);
|
||||||
|
state.from = vp.from;
|
||||||
|
}
|
||||||
|
if (vp.to > state.to) {
|
||||||
|
updateFoldInfo(cm, state.to, vp.to);
|
||||||
|
state.to = vp.to;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, opts.updateViewportTimeSpan || 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFold(cm, from) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var line = from.line;
|
||||||
|
if (line >= state.from && line < state.to)
|
||||||
|
updateFoldInfo(cm, line, line + 1);
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue