searchcursor.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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 SearchCursor(doc, query, pos, caseFold) {
  12. this.atOccurrence = false; this.doc = doc;
  13. if (caseFold == null && typeof query == "string") caseFold = false;
  14. pos = pos ? doc.clipPos(pos) : Pos(0, 0);
  15. this.pos = {from: pos, to: pos};
  16. // The matches method is filled in based on the type of query.
  17. // It takes a position and a direction, and returns an object
  18. // describing the next occurrence of the query, or null if no
  19. // more matches were found.
  20. if (typeof query != "string") { // Regexp match
  21. if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
  22. this.matches = function(reverse, pos) {
  23. if (reverse) {
  24. query.lastIndex = 0;
  25. var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
  26. for (;;) {
  27. query.lastIndex = cutOff;
  28. var newMatch = query.exec(line);
  29. if (!newMatch) break;
  30. match = newMatch;
  31. start = match.index;
  32. cutOff = match.index + (match[0].length || 1);
  33. if (cutOff == line.length) break;
  34. }
  35. var matchLen = (match && match[0].length) || 0;
  36. if (!matchLen) {
  37. if (start == 0 && line.length == 0) {match = undefined;}
  38. else if (start != doc.getLine(pos.line).length) {
  39. matchLen++;
  40. }
  41. }
  42. } else {
  43. query.lastIndex = pos.ch;
  44. var line = doc.getLine(pos.line), match = query.exec(line);
  45. var matchLen = (match && match[0].length) || 0;
  46. var start = match && match.index;
  47. if (start + matchLen != line.length && !matchLen) matchLen = 1;
  48. }
  49. if (match && matchLen)
  50. return {from: Pos(pos.line, start),
  51. to: Pos(pos.line, start + matchLen),
  52. match: match};
  53. };
  54. } else { // String query
  55. var origQuery = query;
  56. if (caseFold) query = query.toLowerCase();
  57. var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
  58. var target = query.split("\n");
  59. // Different methods for single-line and multi-line queries
  60. if (target.length == 1) {
  61. if (!query.length) {
  62. // Empty string would match anything and never progress, so
  63. // we define it to match nothing instead.
  64. this.matches = function() {};
  65. } else {
  66. this.matches = function(reverse, pos) {
  67. if (reverse) {
  68. var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
  69. var match = line.lastIndexOf(query);
  70. if (match > -1) {
  71. match = adjustPos(orig, line, match);
  72. return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
  73. }
  74. } else {
  75. var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
  76. var match = line.indexOf(query);
  77. if (match > -1) {
  78. match = adjustPos(orig, line, match) + pos.ch;
  79. return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
  80. }
  81. }
  82. };
  83. }
  84. } else {
  85. var origTarget = origQuery.split("\n");
  86. this.matches = function(reverse, pos) {
  87. var last = target.length - 1;
  88. if (reverse) {
  89. if (pos.line - (target.length - 1) < doc.firstLine()) return;
  90. if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
  91. var to = Pos(pos.line, origTarget[last].length);
  92. for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
  93. if (target[i] != fold(doc.getLine(ln))) return;
  94. var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
  95. if (fold(line.slice(cut)) != target[0]) return;
  96. return {from: Pos(ln, cut), to: to};
  97. } else {
  98. if (pos.line + (target.length - 1) > doc.lastLine()) return;
  99. var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
  100. if (fold(line.slice(cut)) != target[0]) return;
  101. var from = Pos(pos.line, cut);
  102. for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
  103. if (target[i] != fold(doc.getLine(ln))) return;
  104. if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return;
  105. return {from: from, to: Pos(ln, origTarget[last].length)};
  106. }
  107. };
  108. }
  109. }
  110. }
  111. SearchCursor.prototype = {
  112. findNext: function() {return this.find(false);},
  113. findPrevious: function() {return this.find(true);},
  114. find: function(reverse) {
  115. var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
  116. function savePosAndFail(line) {
  117. var pos = Pos(line, 0);
  118. self.pos = {from: pos, to: pos};
  119. self.atOccurrence = false;
  120. return false;
  121. }
  122. for (;;) {
  123. if (this.pos = this.matches(reverse, pos)) {
  124. this.atOccurrence = true;
  125. return this.pos.match || true;
  126. }
  127. if (reverse) {
  128. if (!pos.line) return savePosAndFail(0);
  129. pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
  130. }
  131. else {
  132. var maxLine = this.doc.lineCount();
  133. if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
  134. pos = Pos(pos.line + 1, 0);
  135. }
  136. }
  137. },
  138. from: function() {if (this.atOccurrence) return this.pos.from;},
  139. to: function() {if (this.atOccurrence) return this.pos.to;},
  140. replace: function(newText) {
  141. if (!this.atOccurrence) return;
  142. var lines = CodeMirror.splitLines(newText);
  143. this.doc.replaceRange(lines, this.pos.from, this.pos.to);
  144. this.pos.to = Pos(this.pos.from.line + lines.length - 1,
  145. lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
  146. }
  147. };
  148. // Maps a position in a case-folded line back to a position in the original line
  149. // (compensating for codepoints increasing in number during folding)
  150. function adjustPos(orig, folded, pos) {
  151. if (orig.length == folded.length) return pos;
  152. for (var pos1 = Math.min(pos, orig.length);;) {
  153. var len1 = orig.slice(0, pos1).toLowerCase().length;
  154. if (len1 < pos) ++pos1;
  155. else if (len1 > pos) --pos1;
  156. else return pos1;
  157. }
  158. }
  159. CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
  160. return new SearchCursor(this.doc, query, pos, caseFold);
  161. });
  162. CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
  163. return new SearchCursor(this, query, pos, caseFold);
  164. });
  165. CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
  166. var ranges = [], next;
  167. var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
  168. while (next = cur.findNext()) {
  169. if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
  170. ranges.push({anchor: cur.from(), head: cur.to()});
  171. }
  172. if (ranges.length)
  173. this.setSelections(ranges, 0);
  174. });
  175. });