// the tagRangeFinder function is // Copyright (C) 2011 by Daniel Glazman <daniel@glazman.org> // released under the MIT license (../../LICENSE) like the rest of CodeMirror CodeMirror.tagRangeFinder = function(cm, line, hideEnd) {

var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*");

var lineText = cm.getLine(line);
var found = false;
var tag = null;
var pos = 0;
while (!found) {
  pos = lineText.indexOf("<", pos);
  if (-1 == pos) // no tag on line
    return;
  if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag
    pos++;
    continue;
  }
  // ok we weem to have a start tag
  if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name...
    pos++;
    continue;
  }
  var gtPos = lineText.indexOf(">", pos + 1);
  if (-1 == gtPos) { // end of start tag not in line
    var l = line + 1;
    var foundGt = false;
    var lastLine = cm.lineCount();
    while (l < lastLine && !foundGt) {
      var lt = cm.getLine(l);
      var gt = lt.indexOf(">");
      if (-1 != gt) { // found a >
        foundGt = true;
        var slash = lt.lastIndexOf("/", gt);
        if (-1 != slash && slash < gt) {
          var str = lineText.substr(slash, gt - slash + 1);
          if (!str.match( /\/\s*\>/ )) { // yep, that's the end of empty tag
            if (hideEnd === true) l++;
            return l;
          }
        }
      }
      l++;
    }
    found = true;
  }
  else {
    var slashPos = lineText.lastIndexOf("/", gtPos);
    if (-1 == slashPos) { // cannot be empty tag
      found = true;
      // don't continue
    }
    else { // empty tag?
      // check if really empty tag
      var str = lineText.substr(slashPos, gtPos - slashPos + 1);
      if (!str.match( /\/\s*\>/ )) { // finally not empty
        found = true;
        // don't continue
      }
    }
  }
  if (found) {
    var subLine = lineText.substr(pos + 1);
    tag = subLine.match(xmlNAMERegExp);
    if (tag) {
      // we have an element name, wooohooo !
      tag = tag[0];
      // do we have the close tag on same line ???
      if (-1 != lineText.indexOf("</" + tag + ">", pos)) // yep
      {
        found = false;
      }
      // we don't, so we have a candidate...
    }
    else
      found = false;
  }
  if (!found)
    pos++;
}

if (found) {
  var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)";
  var startTagRegExp = new RegExp(startTag, "g");
  var endTag = "</" + tag + ">";
  var depth = 1;
  var l = line + 1;
  var lastLine = cm.lineCount();
  while (l < lastLine) {
    lineText = cm.getLine(l);
    var match = lineText.match(startTagRegExp);
    if (match) {
      for (var i = 0; i < match.length; i++) {
        if (match[i] == endTag)
          depth--;
        else
          depth++;
        if (!depth) {
          if (hideEnd === true) l++;
          return l;
        }
      }
    }
    l++;
  }
  return;
}

};

CodeMirror.braceRangeFinder = function(cm, line, hideEnd) {

var lineText = cm.getLine(line), at = lineText.length, startChar, tokenType;
for (;;) {
  var found = lineText.lastIndexOf("{", at);
  if (found < 0) break;
  tokenType = cm.getTokenAt({line: line, ch: found}).className;
  if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; }
  at = found - 1;
}
if (startChar == null || lineText.lastIndexOf("}") > startChar) return;
var count = 1, lastLine = cm.lineCount(), end;
outer: for (var i = line + 1; i < lastLine; ++i) {
  var text = cm.getLine(i), pos = 0;
  for (;;) {
    var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos);
    if (nextOpen < 0) nextOpen = text.length;
    if (nextClose < 0) nextClose = text.length;
    pos = Math.min(nextOpen, nextClose);
    if (pos == text.length) break;
    if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) {
      if (pos == nextOpen) ++count;
      else if (!--count) { end = i; break outer; }
    }
    ++pos;
  }
}
if (end == null || end == line + 1) return;
if (hideEnd === true) end++;
return end;

};

CodeMirror.indentRangeFinder = function(cm, line) {

var tabSize = cm.getOption("tabSize");
var myIndent = cm.getLineHandle(line).indentation(tabSize), last;
for (var i = line + 1, end = cm.lineCount(); i < end; ++i) {
  var handle = cm.getLineHandle(i);
  if (!/^\s*$/.test(handle.text)) {
    if (handle.indentation(tabSize) <= myIndent) break;
    last = i;
  }
}
if (!last) return null;
return last + 1;

};

CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) {

var folded = [];
if (markText == null) markText = '<div style="position: absolute; left: 2px; color:#600">&#x25bc;</div>%N%';

function isFolded(cm, n) {
  for (var i = 0; i < folded.length; ++i) {
    var start = cm.lineInfo(folded[i].start);
    if (!start) folded.splice(i--, 1);
    else if (start.line == n) return {pos: i, region: folded[i]};
  }
}

function expand(cm, region) {
  cm.clearMarker(region.start);
  for (var i = 0; i < region.hidden.length; ++i)
    cm.showLine(region.hidden[i]);
}

return function(cm, line) {
  cm.operation(function() {
    var known = isFolded(cm, line);
    if (known) {
      folded.splice(known.pos, 1);
      expand(cm, known.region);
    } else {
      var end = rangeFinder(cm, line, hideEnd);
      if (end == null) return;
      var hidden = [];
      for (var i = line + 1; i < end; ++i) {
        var handle = cm.hideLine(i);
        if (handle) hidden.push(handle);
      }
      var first = cm.setMarker(line, markText);
      var region = {start: first, hidden: hidden};
      cm.onDeleteLine(first, function() { expand(cm, region); });
      folded.push(region);
    }
  });
};

};