| // 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">▼</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); |
| } |
| }); |
| }; |
| }; |