// CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/LICENSE ;(function (mod) { if (typeof exports == 'object' && typeof module == 'object') // CommonJS mod(require('../../lib/codemirror')) else if (typeof define == 'function' && define.amd) // AMD define(['../../lib/codemirror'], mod) // Plain browser env else mod(CodeMirror) })(function (CodeMirror) { 'use strict' var Pos = CodeMirror.Pos function cmp(a, b) { return a.line - b.line || a.ch - b.ch } 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 xmlTagStart = new RegExp('<(/?)([' + nameStartChar + '][' + nameChar + ']*)', 'g') function Iter(cm, line, ch, range) { this.line = line this.ch = ch this.cm = cm this.text = cm.getLine(line) this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine() this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine() } function tagAt(iter, ch) { var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch)) return type && /\btag\b/.test(type) } function nextLine(iter) { if (iter.line >= iter.max) return iter.ch = 0 iter.text = iter.cm.getLine(++iter.line) return true } function prevLine(iter) { if (iter.line <= iter.min) return iter.text = iter.cm.getLine(--iter.line) iter.ch = iter.text.length return true } function toTagEnd(iter) { for (;;) { var gt = iter.text.indexOf('>', iter.ch) if (gt == -1) { if (nextLine(iter)) continue else return } if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1 continue } var lastSlash = iter.text.lastIndexOf('/', gt) var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)) iter.ch = gt + 1 return selfClose ? 'selfClose' : 'regular' } } function toTagStart(iter) { for (;;) { var lt = iter.ch ? iter.text.lastIndexOf('<', iter.ch - 1) : -1 if (lt == -1) { if (prevLine(iter)) continue else return } if (!tagAt(iter, lt + 1)) { iter.ch = lt continue } xmlTagStart.lastIndex = lt iter.ch = lt var match = xmlTagStart.exec(iter.text) if (match && match.index == lt) return match } } function toNextTag(iter) { for (;;) { xmlTagStart.lastIndex = iter.ch var found = xmlTagStart.exec(iter.text) if (!found) { if (nextLine(iter)) continue else return } if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1 continue } iter.ch = found.index + found[0].length return found } } function toPrevTag(iter) { for (;;) { var gt = iter.ch ? iter.text.lastIndexOf('>', iter.ch - 1) : -1 if (gt == -1) { if (prevLine(iter)) continue else return } if (!tagAt(iter, gt + 1)) { iter.ch = gt continue } var lastSlash = iter.text.lastIndexOf('/', gt) var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)) iter.ch = gt + 1 return selfClose ? 'selfClose' : 'regular' } } function findMatchingClose(iter, tag) { var stack = [] for (;;) { var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0) if (!next || !(end = toTagEnd(iter))) return if (end == 'selfClose') continue if (next[1]) { // closing tag for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) { stack.length = i break } if (i < 0 && (!tag || tag == next[2])) return { tag: next[2], from: Pos(startLine, startCh), to: Pos(iter.line, iter.ch), } } else { // opening tag stack.push(next[2]) } } } function findMatchingOpen(iter, tag) { var stack = [] for (;;) { var prev = toPrevTag(iter) if (!prev) return if (prev == 'selfClose') { toTagStart(iter) continue } var endLine = iter.line, endCh = iter.ch var start = toTagStart(iter) if (!start) return if (start[1]) { // closing tag stack.push(start[2]) } else { // opening tag for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) { stack.length = i break } if (i < 0 && (!tag || tag == start[2])) return { tag: start[2], from: Pos(iter.line, iter.ch), to: Pos(endLine, endCh), } } } } CodeMirror.registerHelper('fold', 'xml', function (cm, start) { var iter = new Iter(cm, start.line, 0) for (;;) { var openTag = toNextTag(iter) if (!openTag || iter.line != start.line) return var end = toTagEnd(iter) if (!end) return if (!openTag[1] && end != 'selfClose') { var startPos = Pos(iter.line, iter.ch) var endPos = findMatchingClose(iter, openTag[2]) return endPos && cmp(endPos.from, startPos) > 0 ? { from: startPos, to: endPos.from } : null } } }) CodeMirror.findMatchingTag = function (cm, pos, range) { var iter = new Iter(cm, pos.line, pos.ch, range) if (iter.text.indexOf('>') == -1 && iter.text.indexOf('<') == -1) return var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch) var start = end && toTagStart(iter) if (!end || !start || cmp(iter, pos) > 0) return var here = { from: Pos(iter.line, iter.ch), to: to, tag: start[2] } if (end == 'selfClose') return { open: here, close: null, at: 'open' } if (start[1]) { // closing tag return { open: findMatchingOpen(iter, start[2]), close: here, at: 'close' } } else { // opening tag iter = new Iter(cm, to.line, to.ch, range) return { open: here, close: findMatchingClose(iter, start[2]), at: 'open' } } } CodeMirror.findEnclosingTag = function (cm, pos, range, tag) { var iter = new Iter(cm, pos.line, pos.ch, range) for (;;) { var open = findMatchingOpen(iter, tag) if (!open) break var forward = new Iter(cm, pos.line, pos.ch, range) var close = findMatchingClose(forward, open.tag) if (close) return { open: open, close: close } } } // Used by addon/edit/closetag.js CodeMirror.scanForClosingTag = function (cm, pos, name, end) { var iter = new Iter(cm, pos.line, pos.ch, end ? { from: 0, to: end } : null) return findMatchingClose(iter, name) } })