blob: 979bccea906ac76b35e7bb93504e006eede9ac80 [file] [log] [blame]
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://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);
}
});