text: // 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", updateInViewport);
      }
      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", updateInViewport);
      }
    });
  
    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.findMarksAt(Pos(line));
      for (var i = 0; i < marks.length; ++i)
        if (marks[i].__isFold && marks[i].find().from.line == line) return true;
    }
  
    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 opts = cm.state.foldGutter.options;
      if (gutter != opts.gutter) return;
      cm.foldCode(Pos(line, 0), opts.rangeFinder);
    }
  
    function onChange(cm) {
      var state = cm.state.foldGutter, opts = cm.state.foldGutter.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, opts = cm.state.foldGutter.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, line = from.line;
      if (line >= state.from && line < state.to)
        updateFoldInfo(cm, line, line + 1);
    }
  });
  