1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others |
---|
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE |
---|
3 | |
---|
4 | (function(mod) { |
---|
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS |
---|
6 | mod(require("../../lib/codemirror")); |
---|
7 | else if (typeof define == "function" && define.amd) // AMD |
---|
8 | define(["../../lib/codemirror"], mod); |
---|
9 | else // Plain browser env |
---|
10 | mod(CodeMirror); |
---|
11 | })(function(CodeMirror) { |
---|
12 | "use strict"; |
---|
13 | |
---|
14 | var Pos = CodeMirror.Pos; |
---|
15 | function cmp(a, b) { return a.line - b.line || a.ch - b.ch; } |
---|
16 | |
---|
17 | 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"; |
---|
18 | var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; |
---|
19 | var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g"); |
---|
20 | |
---|
21 | function Iter(cm, line, ch, range) { |
---|
22 | this.line = line; this.ch = ch; |
---|
23 | this.cm = cm; this.text = cm.getLine(line); |
---|
24 | this.min = range ? range.from : cm.firstLine(); |
---|
25 | this.max = range ? range.to - 1 : cm.lastLine(); |
---|
26 | } |
---|
27 | |
---|
28 | function tagAt(iter, ch) { |
---|
29 | var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch)); |
---|
30 | return type && /\btag\b/.test(type); |
---|
31 | } |
---|
32 | |
---|
33 | function nextLine(iter) { |
---|
34 | if (iter.line >= iter.max) return; |
---|
35 | iter.ch = 0; |
---|
36 | iter.text = iter.cm.getLine(++iter.line); |
---|
37 | return true; |
---|
38 | } |
---|
39 | function prevLine(iter) { |
---|
40 | if (iter.line <= iter.min) return; |
---|
41 | iter.text = iter.cm.getLine(--iter.line); |
---|
42 | iter.ch = iter.text.length; |
---|
43 | return true; |
---|
44 | } |
---|
45 | |
---|
46 | function toTagEnd(iter) { |
---|
47 | for (;;) { |
---|
48 | var gt = iter.text.indexOf(">", iter.ch); |
---|
49 | if (gt == -1) { if (nextLine(iter)) continue; else return; } |
---|
50 | if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; } |
---|
51 | var lastSlash = iter.text.lastIndexOf("/", gt); |
---|
52 | var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)); |
---|
53 | iter.ch = gt + 1; |
---|
54 | return selfClose ? "selfClose" : "regular"; |
---|
55 | } |
---|
56 | } |
---|
57 | function toTagStart(iter) { |
---|
58 | for (;;) { |
---|
59 | var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1; |
---|
60 | if (lt == -1) { if (prevLine(iter)) continue; else return; } |
---|
61 | if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; } |
---|
62 | xmlTagStart.lastIndex = lt; |
---|
63 | iter.ch = lt; |
---|
64 | var match = xmlTagStart.exec(iter.text); |
---|
65 | if (match && match.index == lt) return match; |
---|
66 | } |
---|
67 | } |
---|
68 | |
---|
69 | function toNextTag(iter) { |
---|
70 | for (;;) { |
---|
71 | xmlTagStart.lastIndex = iter.ch; |
---|
72 | var found = xmlTagStart.exec(iter.text); |
---|
73 | if (!found) { if (nextLine(iter)) continue; else return; } |
---|
74 | if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; } |
---|
75 | iter.ch = found.index + found[0].length; |
---|
76 | return found; |
---|
77 | } |
---|
78 | } |
---|
79 | function toPrevTag(iter) { |
---|
80 | for (;;) { |
---|
81 | var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1; |
---|
82 | if (gt == -1) { if (prevLine(iter)) continue; else return; } |
---|
83 | if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; } |
---|
84 | var lastSlash = iter.text.lastIndexOf("/", gt); |
---|
85 | var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)); |
---|
86 | iter.ch = gt + 1; |
---|
87 | return selfClose ? "selfClose" : "regular"; |
---|
88 | } |
---|
89 | } |
---|
90 | |
---|
91 | function findMatchingClose(iter, tag) { |
---|
92 | var stack = []; |
---|
93 | for (;;) { |
---|
94 | var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0); |
---|
95 | if (!next || !(end = toTagEnd(iter))) return; |
---|
96 | if (end == "selfClose") continue; |
---|
97 | if (next[1]) { // closing tag |
---|
98 | for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) { |
---|
99 | stack.length = i; |
---|
100 | break; |
---|
101 | } |
---|
102 | if (i < 0 && (!tag || tag == next[2])) return { |
---|
103 | tag: next[2], |
---|
104 | from: Pos(startLine, startCh), |
---|
105 | to: Pos(iter.line, iter.ch) |
---|
106 | }; |
---|
107 | } else { // opening tag |
---|
108 | stack.push(next[2]); |
---|
109 | } |
---|
110 | } |
---|
111 | } |
---|
112 | function findMatchingOpen(iter, tag) { |
---|
113 | var stack = []; |
---|
114 | for (;;) { |
---|
115 | var prev = toPrevTag(iter); |
---|
116 | if (!prev) return; |
---|
117 | if (prev == "selfClose") { toTagStart(iter); continue; } |
---|
118 | var endLine = iter.line, endCh = iter.ch; |
---|
119 | var start = toTagStart(iter); |
---|
120 | if (!start) return; |
---|
121 | if (start[1]) { // closing tag |
---|
122 | stack.push(start[2]); |
---|
123 | } else { // opening tag |
---|
124 | for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) { |
---|
125 | stack.length = i; |
---|
126 | break; |
---|
127 | } |
---|
128 | if (i < 0 && (!tag || tag == start[2])) return { |
---|
129 | tag: start[2], |
---|
130 | from: Pos(iter.line, iter.ch), |
---|
131 | to: Pos(endLine, endCh) |
---|
132 | }; |
---|
133 | } |
---|
134 | } |
---|
135 | } |
---|
136 | |
---|
137 | CodeMirror.registerHelper("fold", "xml", function(cm, start) { |
---|
138 | var iter = new Iter(cm, start.line, 0); |
---|
139 | for (;;) { |
---|
140 | var openTag = toNextTag(iter), end; |
---|
141 | if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return; |
---|
142 | if (!openTag[1] && end != "selfClose") { |
---|
143 | var start = Pos(iter.line, iter.ch); |
---|
144 | var close = findMatchingClose(iter, openTag[2]); |
---|
145 | return close && {from: start, to: close.from}; |
---|
146 | } |
---|
147 | } |
---|
148 | }); |
---|
149 | CodeMirror.findMatchingTag = function(cm, pos, range) { |
---|
150 | var iter = new Iter(cm, pos.line, pos.ch, range); |
---|
151 | if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return; |
---|
152 | var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch); |
---|
153 | var start = end && toTagStart(iter); |
---|
154 | if (!end || !start || cmp(iter, pos) > 0) return; |
---|
155 | var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]}; |
---|
156 | if (end == "selfClose") return {open: here, close: null, at: "open"}; |
---|
157 | |
---|
158 | if (start[1]) { // closing tag |
---|
159 | return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"}; |
---|
160 | } else { // opening tag |
---|
161 | iter = new Iter(cm, to.line, to.ch, range); |
---|
162 | return {open: here, close: findMatchingClose(iter, start[2]), at: "open"}; |
---|
163 | } |
---|
164 | }; |
---|
165 | |
---|
166 | CodeMirror.findEnclosingTag = function(cm, pos, range) { |
---|
167 | var iter = new Iter(cm, pos.line, pos.ch, range); |
---|
168 | for (;;) { |
---|
169 | var open = findMatchingOpen(iter); |
---|
170 | if (!open) break; |
---|
171 | var forward = new Iter(cm, pos.line, pos.ch, range); |
---|
172 | var close = findMatchingClose(forward, open.tag); |
---|
173 | if (close) return {open: open, close: close}; |
---|
174 | } |
---|
175 | }; |
---|
176 | |
---|
177 | // Used by addon/edit/closetag.js |
---|
178 | CodeMirror.scanForClosingTag = function(cm, pos, name, end) { |
---|
179 | var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null); |
---|
180 | return findMatchingClose(iter, name); |
---|
181 | }; |
---|
182 | }); |
---|