searchcursor.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. (function(){
  2. var Pos = CodeMirror.Pos;
  3. function SearchCursor(doc, query, pos, caseFold) {
  4. this.atOccurrence = false; this.doc = doc;
  5. if (caseFold == null && typeof query == "string") caseFold = false;
  6. pos = pos ? doc.clipPos(pos) : Pos(0, 0);
  7. this.pos = {from: pos, to: pos};
  8. // The matches method is filled in based on the type of query.
  9. // It takes a position and a direction, and returns an object
  10. // describing the next occurrence of the query, or null if no
  11. // more matches were found.
  12. if (typeof query != "string") { // Regexp match
  13. if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
  14. this.matches = function(reverse, pos) {
  15. if (reverse) {
  16. query.lastIndex = 0;
  17. var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
  18. for (;;) {
  19. query.lastIndex = cutOff;
  20. var newMatch = query.exec(line);
  21. if (!newMatch) break;
  22. match = newMatch;
  23. start = match.index;
  24. cutOff = match.index + (match[0].length || 1);
  25. if (cutOff == line.length) break;
  26. }
  27. var matchLen = (match && match[0].length) || 0;
  28. if (!matchLen) {
  29. if (start == 0 && line.length == 0) {match = undefined;}
  30. else if (start != doc.getLine(pos.line).length) {
  31. matchLen++;
  32. }
  33. }
  34. } else {
  35. query.lastIndex = pos.ch;
  36. var line = doc.getLine(pos.line), match = query.exec(line);
  37. var matchLen = (match && match[0].length) || 0;
  38. var start = match && match.index;
  39. if (start + matchLen != line.length && !matchLen) matchLen = 1;
  40. }
  41. if (match && matchLen)
  42. return {from: Pos(pos.line, start),
  43. to: Pos(pos.line, start + matchLen),
  44. match: match};
  45. };
  46. } else { // String query
  47. if (caseFold) query = query.toLowerCase();
  48. var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
  49. var target = query.split("\n");
  50. // Different methods for single-line and multi-line queries
  51. if (target.length == 1) {
  52. if (!query.length) {
  53. // Empty string would match anything and never progress, so
  54. // we define it to match nothing instead.
  55. this.matches = function() {};
  56. } else {
  57. this.matches = function(reverse, pos) {
  58. var line = fold(doc.getLine(pos.line)), len = query.length, match;
  59. if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
  60. : (match = line.indexOf(query, pos.ch)) != -1)
  61. return {from: Pos(pos.line, match),
  62. to: Pos(pos.line, match + len)};
  63. };
  64. }
  65. } else {
  66. this.matches = function(reverse, pos) {
  67. var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln));
  68. var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
  69. if (reverse ? offsetA >= pos.ch || offsetA != match.length
  70. : offsetA <= pos.ch || offsetA != line.length - match.length)
  71. return;
  72. for (;;) {
  73. if (reverse ? !ln : ln == doc.lineCount() - 1) return;
  74. line = fold(doc.getLine(ln += reverse ? -1 : 1));
  75. match = target[reverse ? --idx : ++idx];
  76. if (idx > 0 && idx < target.length - 1) {
  77. if (line != match) return;
  78. else continue;
  79. }
  80. var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
  81. if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
  82. return;
  83. var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
  84. return {from: reverse ? end : start, to: reverse ? start : end};
  85. }
  86. };
  87. }
  88. }
  89. }
  90. SearchCursor.prototype = {
  91. findNext: function() {return this.find(false);},
  92. findPrevious: function() {return this.find(true);},
  93. find: function(reverse) {
  94. var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
  95. function savePosAndFail(line) {
  96. var pos = Pos(line, 0);
  97. self.pos = {from: pos, to: pos};
  98. self.atOccurrence = false;
  99. return false;
  100. }
  101. for (;;) {
  102. if (this.pos = this.matches(reverse, pos)) {
  103. if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
  104. this.atOccurrence = true;
  105. return this.pos.match || true;
  106. }
  107. if (reverse) {
  108. if (!pos.line) return savePosAndFail(0);
  109. pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
  110. }
  111. else {
  112. var maxLine = this.doc.lineCount();
  113. if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
  114. pos = Pos(pos.line + 1, 0);
  115. }
  116. }
  117. },
  118. from: function() {if (this.atOccurrence) return this.pos.from;},
  119. to: function() {if (this.atOccurrence) return this.pos.to;},
  120. replace: function(newText) {
  121. if (!this.atOccurrence) return;
  122. var lines = CodeMirror.splitLines(newText);
  123. this.doc.replaceRange(lines, this.pos.from, this.pos.to);
  124. this.pos.to = Pos(this.pos.from.line + lines.length - 1,
  125. lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
  126. }
  127. };
  128. CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
  129. return new SearchCursor(this.doc, query, pos, caseFold);
  130. });
  131. CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
  132. return new SearchCursor(this, query, pos, caseFold);
  133. });
  134. })();