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