blob: d91cba580abeb6791e622b1e0d14939870ea872b [file] [log] [blame]
adminbae64d82013-08-01 10:50:15 -07001// the tagRangeFinder function is
2// Copyright (C) 2011 by Daniel Glazman <daniel@glazman.org>
3// released under the MIT license (../../LICENSE) like the rest of CodeMirror
4CodeMirror.tagRangeFinder = function(cm, line, hideEnd) {
5 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";
6 var nameChar = nameStartChar + "\-\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
7 var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*");
8
9 var lineText = cm.getLine(line);
10 var found = false;
11 var tag = null;
12 var pos = 0;
13 while (!found) {
14 pos = lineText.indexOf("<", pos);
15 if (-1 == pos) // no tag on line
16 return;
17 if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag
18 pos++;
19 continue;
20 }
21 // ok we weem to have a start tag
22 if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name...
23 pos++;
24 continue;
25 }
26 var gtPos = lineText.indexOf(">", pos + 1);
27 if (-1 == gtPos) { // end of start tag not in line
28 var l = line + 1;
29 var foundGt = false;
30 var lastLine = cm.lineCount();
31 while (l < lastLine && !foundGt) {
32 var lt = cm.getLine(l);
33 var gt = lt.indexOf(">");
34 if (-1 != gt) { // found a >
35 foundGt = true;
36 var slash = lt.lastIndexOf("/", gt);
37 if (-1 != slash && slash < gt) {
38 var str = lineText.substr(slash, gt - slash + 1);
39 if (!str.match( /\/\s*\>/ )) { // yep, that's the end of empty tag
40 if (hideEnd === true) l++;
41 return l;
42 }
43 }
44 }
45 l++;
46 }
47 found = true;
48 }
49 else {
50 var slashPos = lineText.lastIndexOf("/", gtPos);
51 if (-1 == slashPos) { // cannot be empty tag
52 found = true;
53 // don't continue
54 }
55 else { // empty tag?
56 // check if really empty tag
57 var str = lineText.substr(slashPos, gtPos - slashPos + 1);
58 if (!str.match( /\/\s*\>/ )) { // finally not empty
59 found = true;
60 // don't continue
61 }
62 }
63 }
64 if (found) {
65 var subLine = lineText.substr(pos + 1);
66 tag = subLine.match(xmlNAMERegExp);
67 if (tag) {
68 // we have an element name, wooohooo !
69 tag = tag[0];
70 // do we have the close tag on same line ???
71 if (-1 != lineText.indexOf("</" + tag + ">", pos)) // yep
72 {
73 found = false;
74 }
75 // we don't, so we have a candidate...
76 }
77 else
78 found = false;
79
80 }
81 if (!found)
82 pos++;
83 }
84
85 if (found) {
86 var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)";
87 var startTagRegExp = new RegExp(startTag, "g");
88 var endTag = "</" + tag + ">";
89 var depth = 1;
90 var l = line + 1;
91 var lastLine = cm.lineCount();
92 while (l < lastLine) {
93 lineText = cm.getLine(l);
94 var match = lineText.match(startTagRegExp);
95 if (match) {
96 for (var i = 0; i < match.length; i++) {
97 if (match[i] == endTag)
98 depth--;
99 else
100 depth++;
101 if (!depth) {
102 if (hideEnd === true) l++;
103 return l;
104 }
105 }
106 }
107 l++;
108 }
109 return;
110 }
111};
112
113CodeMirror.braceRangeFinder = function(cm, line, hideEnd) {
114 var lineText = cm.getLine(line);
115 var startChar = lineText.lastIndexOf(" ");
116 if (startChar < 0 || lineText.lastIndexOf(" ") > startChar) return;
117 var tokenType = cm.getTokenAt({line: line, ch: startChar}).className;
118 var count = 1, lastLine = cm.lineCount(), end;
119 outer: for (var i = line + 1; i < lastLine; ++i) {
120 var text = cm.getLine(i), pos = 0;
121 for (;;) {
122 var nextOpen = text.indexOf("", pos), nextClose = text.indexOf("", pos);
123 if (nextOpen < 0) nextOpen = text.length;
124 if (nextClose < 0) nextClose = text.length;
125 pos = Math.min(nextOpen, nextClose);
126 if (pos == text.length) break;
127 if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) {
128 if (pos == nextOpen) ++count;
129 else if (!--count) { end = i; break outer; }
130 }
131 ++pos;
132 }
133 }
134 if (end == null || end == line + 1) return;
135 if (hideEnd === true) end++;
136 return end;
137};
138
139CodeMirror.braceRangeFinde = function(cm, line, hideEnd) {
140 var lineText = cm.getLine(line);
141 var startChar = lineText.lastIndexOf("{");
142 if (startChar < 0 || lineText.lastIndexOf("}") > startChar) return;
143 var tokenType = cm.getTokenAt({line: line, ch: startChar}).className;
144 var count = 1, lastLine = cm.lineCount(), end;
145 outer: for (var i = line + 1; i < lastLine; ++i) {
146 var text = cm.getLine(i), pos = 0;
147 for (;;) {
148 var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos);
149 if (nextOpen < 0) nextOpen = text.length;
150 if (nextClose < 0) nextClose = text.length;
151 pos = Math.min(nextOpen, nextClose);
152 if (pos == text.length) break;
153 if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) {
154 if (pos == nextOpen) ++count;
155 else if (!--count) { end = i; break outer; }
156 }
157 ++pos;
158 }
159 }
160 if (end == null || end == line + 1) return;
161 if (hideEnd === true) end++;
162 return end;
163};
164
165CodeMirror.indentRangeFinder = function(cm, line) {
166 var tabSize = cm.getOption("tabSize");
167 var myIndent = cm.getLineHandle(line).indentation(tabSize), last;
168 for (var i = line + 1, end = cm.lineCount(); i < end; ++i) {
169 var handle = cm.getLineHandle(i);
170 if (!/^\s*$/.test(handle.text)) {
171 if (handle.indentation(tabSize) <= myIndent) break;
172 last = i;
173 }
174 }
175 if (!last) return null;
176 return last + 1;
177};
178
179CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) {
180 var folded = [];
181 if (markText == null) markText = '<div style="position: absolute; left: 2px; color:#600">&#x25bc;</div>%N%';
182
183 function isFolded(cm, n) {
184 for (var i = 0; i < folded.length; ++i) {
185 var start = cm.lineInfo(folded[i].start);
186 if (!start) folded.splice(i--, 1);
187 else if (start.line == n) return {pos: i, region: folded[i]};
188 }
189 }
190
191 function expand(cm, region) {
192 cm.clearMarker(region.start);
193 for (var i = 0; i < region.hidden.length; ++i)
194 cm.showLine(region.hidden[i]);
195 }
196
197 return function(cm, line) {
198 cm.operation(function() {
199 var known = isFolded(cm, line);
200 if (known) {
201 folded.splice(known.pos, 1);
202 expand(cm, known.region);
203 } else {
204 var end = rangeFinder(cm, line, hideEnd);
205 if (end == null) return;
206 var hidden = [];
207 for (var i = line + 1; i < end; ++i) {
208 var handle = cm.hideLine(i);
209 if (handle) hidden.push(handle);
210 }
211 var first = cm.setMarker(line, markText);
212 var region = {start: first, hidden: hidden};
213 cm.onDeleteLine(first, function() { expand(cm, region); });
214 folded.push(region);
215 }
216 });
217 };
218};