xml-fold.js 6.2 KB

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