text: /* ***** BEGIN LICENSE BLOCK *****
   * Distributed under the BSD license:
   *
   * Copyright (c) 2010, Ajax.org B.V.
   * All rights reserved.
   * 
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     * Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   *     * Neither the name of Ajax.org B.V. nor the
   *       names of its contributors may be used to endorse or promote products
   *       derived from this software without specific prior written permission.
   * 
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * ***** END LICENSE BLOCK ***** */
  
  define(function(require, exports, module) {
  "use strict";
  
  var Range = require("../range").Range;
  var FoldLine = require("./fold_line").FoldLine;
  var Fold = require("./fold").Fold;
  var TokenIterator = require("../token_iterator").TokenIterator;
  
  function Folding() {
      /*
       * Looks up a fold at a given row/column. Possible values for side:
       *   -1: ignore a fold if fold.start = row/column
       *   +1: ignore a fold if fold.end = row/column
       */
      this.getFoldAt = function(row, column, side) {
          var foldLine = this.getFoldLine(row);
          if (!foldLine)
              return null;
  
          var folds = foldLine.folds;
          for (var i = 0; i < folds.length; i++) {
              var fold = folds[i];
              if (fold.range.contains(row, column)) {
                  if (side == 1 && fold.range.isEnd(row, column)) {
                      continue;
                  } else if (side == -1 && fold.range.isStart(row, column)) {
                      continue;
                  }
                  return fold;
              }
          }
      };
  
      /*
       * Returns all folds in the given range. Note, that this will return folds
       *
       */
      this.getFoldsInRange = function(range) {
          var start = range.start;
          var end = range.end;
          var foldLines = this.$foldData;
          var foundFolds = [];
  
          start.column += 1;
          end.column -= 1;
  
          for (var i = 0; i < foldLines.length; i++) {
              var cmp = foldLines[i].range.compareRange(range);
              if (cmp == 2) {
                  // Range is before foldLine. No intersection. This means,
                  // there might be other foldLines that intersect.
                  continue;
              }
              else if (cmp == -2) {
                  // Range is after foldLine. There can't be any other foldLines then,
                  // so let's give up.
                  break;
              }
  
              var folds = foldLines[i].folds;
              for (var j = 0; j < folds.length; j++) {
                  var fold = folds[j];
                  cmp = fold.range.compareRange(range);
                  if (cmp == -2) {
                      break;
                  } else if (cmp == 2) {
                      continue;
                  } else
                  // WTF-state: Can happen due to -1/+1 to start/end column.
                  if (cmp == 42) {
                      break;
                  }
                  foundFolds.push(fold);
              }
          }
          start.column -= 1;
          end.column += 1;
  
          return foundFolds;
      };
  
      this.getFoldsInRangeList = function(ranges) {
          if (Array.isArray(ranges)) {
              var folds = [];
              ranges.forEach(function(range) {
                  folds = folds.concat(this.getFoldsInRange(range));
              }, this);
          } else {
              var folds = this.getFoldsInRange(ranges);
          }
          return folds;
      };
      
      /*
       * Returns all folds in the document
       */
      this.getAllFolds = function() {
          var folds = [];
          var foldLines = this.$foldData;
          
          for (var i = 0; i < foldLines.length; i++)
              for (var j = 0; j < foldLines[i].folds.length; j++)
                  folds.push(foldLines[i].folds[j]);
  
          return folds;
      };
  
      /*
       * Returns the string between folds at the given position.
       * E.g.
       *  foo<fold>b|ar<fold>wolrd -> "bar"
       *  foo<fold>bar<fold>wol|rd -> "world"
       *  foo<fold>bar<fo|ld>wolrd -> <null>
       *
       * where | means the position of row/column
       *
       * The trim option determs if the return string should be trimed according
       * to the "side" passed with the trim value:
       *
       * E.g.
       *  foo<fold>b|ar<fold>wolrd -trim=-1> "b"
       *  foo<fold>bar<fold>wol|rd -trim=+1> "rld"
       *  fo|o<fold>bar<fold>wolrd -trim=00> "foo"
       */
      this.getFoldStringAt = function(row, column, trim, foldLine) {
          foldLine = foldLine || this.getFoldLine(row);
          if (!foldLine)
              return null;
  
          var lastFold = {
              end: { column: 0 }
          };
          // TODO: Refactor to use getNextFoldTo function.
          var str, fold;
          for (var i = 0; i < foldLine.folds.length; i++) {
              fold = foldLine.folds[i];
              var cmp = fold.range.compareEnd(row, column);
              if (cmp == -1) {
                  str = this
                      .getLine(fold.start.row)
                      .substring(lastFold.end.column, fold.start.column);
                  break;
              }
              else if (cmp === 0) {
                  return null;
              }
              lastFold = fold;
          }
          if (!str)
              str = this.getLine(fold.start.row).substring(lastFold.end.column);
  
          if (trim == -1)
              return str.substring(0, column - lastFold.end.column);
          else if (trim == 1)
              return str.substring(column - lastFold.end.column);
          else
              return str;
      };
  
      this.getFoldLine = function(docRow, startFoldLine) {
          var foldData = this.$foldData;
          var i = 0;
          if (startFoldLine)
              i = foldData.indexOf(startFoldLine);
          if (i == -1)
              i = 0;
          for (i; i < foldData.length; i++) {
              var foldLine = foldData[i];
              if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) {
                  return foldLine;
              } else if (foldLine.end.row > docRow) {
                  return null;
              }
          }
          return null;
      };
  
      // returns the fold which starts after or contains docRow
      this.getNextFoldLine = function(docRow, startFoldLine) {
          var foldData = this.$foldData;
          var i = 0;
          if (startFoldLine)
              i = foldData.indexOf(startFoldLine);
          if (i == -1)
              i = 0;
          for (i; i < foldData.length; i++) {
              var foldLine = foldData[i];
              if (foldLine.end.row >= docRow) {
                  return foldLine;
              }
          }
          return null;
      };
  
      this.getFoldedRowCount = function(first, last) {
          var foldData = this.$foldData, rowCount = last-first+1;
          for (var i = 0; i < foldData.length; i++) {
              var foldLine = foldData[i],
                  end = foldLine.end.row,
                  start = foldLine.start.row;
              if (end >= last) {
                  if (start < last) {
                      if (start >= first)
                          rowCount -= last-start;
                      else
                          rowCount = 0; // in one fold
                  }
                  break;
              } else if (end >= first){
                  if (start >= first) // fold inside range
                      rowCount -=  end-start;
                  else
                      rowCount -=  end-first+1;
              }
          }
          return rowCount;
      };
  
      this.$addFoldLine = function(foldLine) {
          this.$foldData.push(foldLine);
          this.$foldData.sort(function(a, b) {
              return a.start.row - b.start.row;
          });
          return foldLine;
      };
  
      /**
       * Adds a new fold.
       *
       * @returns
       *      The new created Fold object or an existing fold object in case the
       *      passed in range fits an existing fold exactly.
       */
      this.addFold = function(placeholder, range) {
          var foldData = this.$foldData;
          var added = false;
          var fold;
          
          if (placeholder instanceof Fold)
              fold = placeholder;
          else {
              fold = new Fold(range, placeholder);
              fold.collapseChildren = range.collapseChildren;
          }
          this.$clipRangeToDocument(fold.range);
  
          var startRow = fold.start.row;
          var startColumn = fold.start.column;
          var endRow = fold.end.row;
          var endColumn = fold.end.column;
  
          // --- Some checking ---
          if (!(startRow < endRow || 
              startRow == endRow && startColumn <= endColumn - 2))
              throw new Error("The range has to be at least 2 characters width");
  
          var startFold = this.getFoldAt(startRow, startColumn, 1);
          var endFold = this.getFoldAt(endRow, endColumn, -1);
          if (startFold && endFold == startFold)
              return startFold.addSubFold(fold);
  
          if (startFold && !startFold.range.isStart(startRow, startColumn))
              this.removeFold(startFold);
          
          if (endFold && !endFold.range.isEnd(endRow, endColumn))
              this.removeFold(endFold);
          
          // Check if there are folds in the range we create the new fold for.
          var folds = this.getFoldsInRange(fold.range);
          if (folds.length > 0) {
              // Remove the folds from fold data.
              this.removeFolds(folds);
              // Add the removed folds as subfolds on the new fold.
              folds.forEach(function(subFold) {
                  fold.addSubFold(subFold);
              });
          }
  
          for (var i = 0; i < foldData.length; i++) {
              var foldLine = foldData[i];
              if (endRow == foldLine.start.row) {
                  foldLine.addFold(fold);
                  added = true;
                  break;
              } else if (startRow == foldLine.end.row) {
                  foldLine.addFold(fold);
                  added = true;
                  if (!fold.sameRow) {
                      // Check if we might have to merge two FoldLines.
                      var foldLineNext = foldData[i + 1];
                      if (foldLineNext && foldLineNext.start.row == endRow) {
                          // We need to merge!
                          foldLine.merge(foldLineNext);
                          break;
                      }
                  }
                  break;
              } else if (endRow <= foldLine.start.row) {
                  break;
              }
          }
  
          if (!added)
              foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold));
  
          if (this.$useWrapMode)
              this.$updateWrapData(foldLine.start.row, foldLine.start.row);
          else
              this.$updateRowLengthCache(foldLine.start.row, foldLine.start.row);
  
          // Notify that fold data has changed.
          this.$modified = true;
          this._signal("changeFold", { data: fold, action: "add" });
  
          return fold;
      };
  
      this.addFolds = function(folds) {
          folds.forEach(function(fold) {
              this.addFold(fold);
          }, this);
      };
  
      this.removeFold = function(fold) {
          var foldLine = fold.foldLine;
          var startRow = foldLine.start.row;
          var endRow = foldLine.end.row;
  
          var foldLines = this.$foldData;
          var folds = foldLine.folds;
          // Simple case where there is only one fold in the FoldLine such that
          // the entire fold line can get removed directly.
          if (folds.length == 1) {
              foldLines.splice(foldLines.indexOf(foldLine), 1);
          } else
          // If the fold is the last fold of the foldLine, just remove it.
          if (foldLine.range.isEnd(fold.end.row, fold.end.column)) {
              folds.pop();
              foldLine.end.row = folds[folds.length - 1].end.row;
              foldLine.end.column = folds[folds.length - 1].end.column;
          } else
          // If the fold is the first fold of the foldLine, just remove it.
          if (foldLine.range.isStart(fold.start.row, fold.start.column)) {
              folds.shift();
              foldLine.start.row = folds[0].start.row;
              foldLine.start.column = folds[0].start.column;
          } else
          // We know there are more then 2 folds and the fold is not at the edge.
          // This means, the fold is somewhere in between.
          //
          // If the fold is in one row, we just can remove it.
          if (fold.sameRow) {
              folds.splice(folds.indexOf(fold), 1);
          } else
          // The fold goes over more then one row. This means remvoing this fold
          // will cause the fold line to get splitted up. newFoldLine is the second part
          {
              var newFoldLine = foldLine.split(fold.start.row, fold.start.column);
              folds = newFoldLine.folds;
              folds.shift();
              newFoldLine.start.row = folds[0].start.row;
              newFoldLine.start.column = folds[0].start.column;
          }
  
          if (!this.$updating) {
              if (this.$useWrapMode)
                  this.$updateWrapData(startRow, endRow);
              else
                  this.$updateRowLengthCache(startRow, endRow);
          }
          
          // Notify that fold data has changed.
          this.$modified = true;
          this._signal("changeFold", { data: fold, action: "remove" });
      };
  
      this.removeFolds = function(folds) {
          // We need to clone the folds array passed in as it might be the folds
          // array of a fold line and as we call this.removeFold(fold), folds
          // are removed from folds and changes the current index.
          var cloneFolds = [];
          for (var i = 0; i < folds.length; i++) {
              cloneFolds.push(folds[i]);
          }
  
          cloneFolds.forEach(function(fold) {
              this.removeFold(fold);
          }, this);
          this.$modified = true;
      };
  
      this.expandFold = function(fold) {
          this.removeFold(fold);
          fold.subFolds.forEach(function(subFold) {
              fold.restoreRange(subFold);
              this.addFold(subFold);
          }, this);
          if (fold.collapseChildren > 0) {
              this.foldAll(fold.start.row+1, fold.end.row, fold.collapseChildren-1);
          }
          fold.subFolds = [];
      };
  
      this.expandFolds = function(folds) {
          folds.forEach(function(fold) {
              this.expandFold(fold);
          }, this);
      };
  
      this.unfold = function(location, expandInner) {
          var range, folds;
          if (location == null) {
              range = new Range(0, 0, this.getLength(), 0);
              expandInner = true;
          } else if (typeof location == "number")
              range = new Range(location, 0, location, this.getLine(location).length);
          else if ("row" in location)
              range = Range.fromPoints(location, location);
          else
              range = location;
          
          folds = this.getFoldsInRangeList(range);
          if (expandInner) {
              this.removeFolds(folds);
          } else {
              var subFolds = folds;
              // TODO: might be better to remove and add folds in one go instead of using
              // expandFolds several times.
              while (subFolds.length) {
                  this.expandFolds(subFolds);
                  subFolds = this.getFoldsInRangeList(range);
              }
          }
          if (folds.length)
              return folds;
      };
  
      /*
       * Checks if a given documentRow is folded. This is true if there are some
       * folded parts such that some parts of the line is still visible.
       **/
      this.isRowFolded = function(docRow, startFoldRow) {
          return !!this.getFoldLine(docRow, startFoldRow);
      };
  
      this.getRowFoldEnd = function(docRow, startFoldRow) {
          var foldLine = this.getFoldLine(docRow, startFoldRow);
          return foldLine ? foldLine.end.row : docRow;
      };
  
      this.getRowFoldStart = function(docRow, startFoldRow) {
          var foldLine = this.getFoldLine(docRow, startFoldRow);
          return foldLine ? foldLine.start.row : docRow;
      };
  
      this.getFoldDisplayLine = function(foldLine, endRow, endColumn, startRow, startColumn) {
          if (startRow == null)
              startRow = foldLine.start.row;
          if (startColumn == null)
              startColumn = 0;
          if (endRow == null)
              endRow = foldLine.end.row;
          if (endColumn == null)
              endColumn = this.getLine(endRow).length;
          
  
          // Build the textline using the FoldLine walker.
          var doc = this.doc;
          var textLine = "";
  
          foldLine.walk(function(placeholder, row, column, lastColumn) {
              if (row < startRow)
                  return;
              if (row == startRow) {
                  if (column < startColumn)
                      return;
                  lastColumn = Math.max(startColumn, lastColumn);
              }
  
              if (placeholder != null) {
                  textLine += placeholder;
              } else {
                  textLine += doc.getLine(row).substring(lastColumn, column);
              }
          }, endRow, endColumn);
          return textLine;
      };
  
      this.getDisplayLine = function(row, endColumn, startRow, startColumn) {
          var foldLine = this.getFoldLine(row);
  
          if (!foldLine) {
              var line;
              line = this.doc.getLine(row);
              return line.substring(startColumn || 0, endColumn || line.length);
          } else {
              return this.getFoldDisplayLine(
                  foldLine, row, endColumn, startRow, startColumn);
          }
      };
  
      this.$cloneFoldData = function() {
          var fd = [];
          fd = this.$foldData.map(function(foldLine) {
              var folds = foldLine.folds.map(function(fold) {
                  return fold.clone();
              });
              return new FoldLine(fd, folds);
          });
  
          return fd;
      };
  
      this.toggleFold = function(tryToUnfold) {
          var selection = this.selection;
          var range = selection.getRange();
          var fold;
          var bracketPos;
  
          if (range.isEmpty()) {
              var cursor = range.start;
              fold = this.getFoldAt(cursor.row, cursor.column);
  
              if (fold) {
                  this.expandFold(fold);
                  return;
              } else if (bracketPos = this.findMatchingBracket(cursor)) {
                  if (range.comparePoint(bracketPos) == 1) {
                      range.end = bracketPos;
                  } else {
                      range.start = bracketPos;
                      range.start.column++;
                      range.end.column--;
                  }
              } else if (bracketPos = this.findMatchingBracket({row: cursor.row, column: cursor.column + 1})) {
                  if (range.comparePoint(bracketPos) == 1)
                      range.end = bracketPos;
                  else
                      range.start = bracketPos;
  
                  range.start.column++;
              } else {
                  range = this.getCommentFoldRange(cursor.row, cursor.column) || range;
              }
          } else {
              var folds = this.getFoldsInRange(range);
              if (tryToUnfold && folds.length) {
                  this.expandFolds(folds);
                  return;
              } else if (folds.length == 1 ) {
                  fold = folds[0];
              }
          }
  
          if (!fold)
              fold = this.getFoldAt(range.start.row, range.start.column);
  
          if (fold && fold.range.toString() == range.toString()) {
              this.expandFold(fold);
              return;
          }
  
          var placeholder = "...";
          if (!range.isMultiLine()) {
              placeholder = this.getTextRange(range);
              if (placeholder.length < 4)
                  return;
              placeholder = placeholder.trim().substring(0, 2) + "..";
          }
  
          this.addFold(placeholder, range);
      };
  
      this.getCommentFoldRange = function(row, column, dir) {
          var iterator = new TokenIterator(this, row, column);
          var token = iterator.getCurrentToken();
          var type = token.type;
          if (token && /^comment|string/.test(type)) {
              type = type.match(/comment|string/)[0];
              if (type == "comment")
                  type += "|doc-start";
              var re = new RegExp(type);
              var range = new Range();
              if (dir != 1) {
                  do {
                      token = iterator.stepBackward();
                  } while (token && re.test(token.type));
                  iterator.stepForward();
              }
              
              range.start.row = iterator.getCurrentTokenRow();
              range.start.column = iterator.getCurrentTokenColumn() + 2;
  
              iterator = new TokenIterator(this, row, column);
              
              if (dir != -1) {
                  var lastRow = -1;
                  do {
                      token = iterator.stepForward();
                      if (lastRow == -1) {
                          var state = this.getState(iterator.$row);
                          if (!re.test(state))
                              lastRow = iterator.$row;
                      } else if (iterator.$row > lastRow) {
                          break;
                      }
                  } while (token && re.test(token.type));
                  token = iterator.stepBackward();
              } else
                  token = iterator.getCurrentToken();
  
              range.end.row = iterator.getCurrentTokenRow();
              range.end.column = iterator.getCurrentTokenColumn() + token.value.length - 2;
              return range;
          }
      };
  
      this.foldAll = function(startRow, endRow, depth) {
          if (depth == undefined)
              depth = 100000; // JSON.stringify doesn't hanle Infinity
          var foldWidgets = this.foldWidgets;
          if (!foldWidgets)
              return; // mode doesn't support folding
          endRow = endRow || this.getLength();
          startRow = startRow || 0;
          for (var row = startRow; row < endRow; row++) {
              if (foldWidgets[row] == null)
                  foldWidgets[row] = this.getFoldWidget(row);
              if (foldWidgets[row] != "start")
                  continue;
  
              var range = this.getFoldWidgetRange(row);
              // sometimes range can be incompatible with existing fold
              // TODO change addFold to return null istead of throwing
              if (range && range.isMultiLine()
                  && range.end.row <= endRow
                  && range.start.row >= startRow
              ) {
                  row = range.end.row;
                  try {
                      // addFold can change the range
                      var fold = this.addFold("...", range);
                      if (fold)
                          fold.collapseChildren = depth;
                  } catch(e) {}
              }
          }
      };
      
      // structured folding
      this.$foldStyles = {
          "manual": 1,
          "markbegin": 1,
          "markbeginend": 1
      };
      this.$foldStyle = "markbegin";
      this.setFoldStyle = function(style) {
          if (!this.$foldStyles[style])
              throw new Error("invalid fold style: " + style + "[" + Object.keys(this.$foldStyles).join(", ") + "]");
          
          if (this.$foldStyle == style)
              return;
  
          this.$foldStyle = style;
          
          if (style == "manual")
              this.unfold();
          
          // reset folding
          var mode = this.$foldMode;
          this.$setFolding(null);
          this.$setFolding(mode);
      };
  
      this.$setFolding = function(foldMode) {
          if (this.$foldMode == foldMode)
              return;
              
          this.$foldMode = foldMode;
          
          this.off('change', this.$updateFoldWidgets);
          this.off('tokenizerUpdate', this.$tokenizerUpdateFoldWidgets);
          this._signal("changeAnnotation");
          
          if (!foldMode || this.$foldStyle == "manual") {
              this.foldWidgets = null;
              return;
          }
          
          this.foldWidgets = [];
          this.getFoldWidget = foldMode.getFoldWidget.bind(foldMode, this, this.$foldStyle);
          this.getFoldWidgetRange = foldMode.getFoldWidgetRange.bind(foldMode, this, this.$foldStyle);
          
          this.$updateFoldWidgets = this.updateFoldWidgets.bind(this);
          this.$tokenizerUpdateFoldWidgets = this.tokenizerUpdateFoldWidgets.bind(this);
          this.on('change', this.$updateFoldWidgets);
          this.on('tokenizerUpdate', this.$tokenizerUpdateFoldWidgets);
      };
  
      this.getParentFoldRangeData = function (row, ignoreCurrent) {
          var fw = this.foldWidgets;
          if (!fw || (ignoreCurrent && fw[row]))
              return {};
  
          var i = row - 1, firstRange;
          while (i >= 0) {
              var c = fw[i];
              if (c == null)
                  c = fw[i] = this.getFoldWidget(i);
  
              if (c == "start") {
                  var range = this.getFoldWidgetRange(i);
                  if (!firstRange)
                      firstRange = range;
                  if (range && range.end.row >= row)
                      break;
              }
              i--;
          }
  
          return {
              range: i !== -1 && range,
              firstRange: firstRange
          };
      };
  
      this.onFoldWidgetClick = function(row, e) {
          e = e.domEvent;
          var options = {
              children: e.shiftKey,
              all: e.ctrlKey || e.metaKey,
              siblings: e.altKey
          };
          
          var range = this.$toggleFoldWidget(row, options);
          if (!range) {
              var el = (e.target || e.srcElement);
              if (el && /ace_fold-widget/.test(el.className))
                  el.className += " ace_invalid";
          }
      };
      
      this.$toggleFoldWidget = function(row, options) {
          if (!this.getFoldWidget)
              return;
          var type = this.getFoldWidget(row);
          var line = this.getLine(row);
  
          var dir = type === "end" ? -1 : 1;
          var fold = this.getFoldAt(row, dir === -1 ? 0 : line.length, dir);
  
          if (fold) {
              if (options.children || options.all)
                  this.removeFold(fold);
              else
                  this.expandFold(fold);
              return fold;
          }
  
          var range = this.getFoldWidgetRange(row, true);
          // sometimes singleline folds can be missed by the code above
          if (range && !range.isMultiLine()) {
              fold = this.getFoldAt(range.start.row, range.start.column, 1);
              if (fold && range.isEqual(fold.range)) {
                  this.removeFold(fold);
                  return fold;
              }
          }
          
          if (options.siblings) {
              var data = this.getParentFoldRangeData(row);
              if (data.range) {
                  var startRow = data.range.start.row + 1;
                  var endRow = data.range.end.row;
              }
              this.foldAll(startRow, endRow, options.all ? 10000 : 0);
          } else if (options.children) {
              endRow = range ? range.end.row : this.getLength();
              this.foldAll(row + 1, endRow, options.all ? 10000 : 0);
          } else if (range) {
              if (options.all) 
                  range.collapseChildren = 10000;
              this.addFold("...", range);
          }
          
          return range;
      };
      
      
      
      this.toggleFoldWidget = function(toggleParent) {
          var row = this.selection.getCursor().row;
          row = this.getRowFoldStart(row);
          var range = this.$toggleFoldWidget(row, {});
          
          if (range)
              return;
          // handle toggleParent
          var data = this.getParentFoldRangeData(row, true);
          range = data.range || data.firstRange;
          
          if (range) {
              row = range.start.row;
              var fold = this.getFoldAt(row, this.getLine(row).length, 1);
  
              if (fold) {
                  this.removeFold(fold);
              } else {
                  this.addFold("...", range);
              }
          }
      };
  
      this.updateFoldWidgets = function(delta) {
          var firstRow = delta.start.row;
          var len = delta.end.row - firstRow;
  
          if (len === 0) {
              this.foldWidgets[firstRow] = null;
          } else if (delta.action == 'remove') {
              this.foldWidgets.splice(firstRow, len + 1, null);
          } else {
              var args = Array(len + 1);
              args.unshift(firstRow, 1);
              this.foldWidgets.splice.apply(this.foldWidgets, args);
          }
      };
      this.tokenizerUpdateFoldWidgets = function(e) {
          var rows = e.data;
          if (rows.first != rows.last) {
              if (this.foldWidgets.length > rows.first)
                  this.foldWidgets.splice(rows.first, this.foldWidgets.length);
          }
      };
  }
  
  exports.Folding = Folding;
  
  });
  