1 |
|
2 |
|
3 |
|
4 | (function(mod) {
|
5 | if (typeof exports == "object" && typeof module == "object")
|
6 | mod(require("../../lib/codemirror"));
|
7 | else if (typeof define == "function" && define.amd)
|
8 | define(["../../lib/codemirror"], mod);
|
9 | else
|
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 ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
|
25 | this.max = range ? Math.min(range.to - 1, cm.lastLine()) : 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]) {
|
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 {
|
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]) {
|
122 | stack.push(start[2]);
|
123 | } else {
|
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)
|
141 | if (!openTag || iter.line != start.line) return
|
142 | var end = toTagEnd(iter)
|
143 | if (!end) return
|
144 | if (!openTag[1] && end != "selfClose") {
|
145 | var startPos = Pos(iter.line, iter.ch);
|
146 | var endPos = findMatchingClose(iter, openTag[2]);
|
147 | return endPos && cmp(endPos.from, startPos) > 0 ? {from: startPos, to: endPos.from} : null
|
148 | }
|
149 | }
|
150 | });
|
151 | CodeMirror.findMatchingTag = function(cm, pos, range) {
|
152 | var iter = new Iter(cm, pos.line, pos.ch, range);
|
153 | if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return;
|
154 | var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);
|
155 | var start = end && toTagStart(iter);
|
156 | if (!end || !start || cmp(iter, pos) > 0) return;
|
157 | var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};
|
158 | if (end == "selfClose") return {open: here, close: null, at: "open"};
|
159 |
|
160 | if (start[1]) {
|
161 | return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"};
|
162 | } else {
|
163 | iter = new Iter(cm, to.line, to.ch, range);
|
164 | return {open: here, close: findMatchingClose(iter, start[2]), at: "open"};
|
165 | }
|
166 | };
|
167 |
|
168 | CodeMirror.findEnclosingTag = function(cm, pos, range, tag) {
|
169 | var iter = new Iter(cm, pos.line, pos.ch, range);
|
170 | for (;;) {
|
171 | var open = findMatchingOpen(iter, tag);
|
172 | if (!open) break;
|
173 | var forward = new Iter(cm, pos.line, pos.ch, range);
|
174 | var close = findMatchingClose(forward, open.tag);
|
175 | if (close) return {open: open, close: close};
|
176 | }
|
177 | };
|
178 |
|
179 |
|
180 | CodeMirror.scanForClosingTag = function(cm, pos, name, end) {
|
181 | var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);
|
182 | return findMatchingClose(iter, name);
|
183 | };
|
184 | });
|