searchcursor.js 7.5 KB

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