searchcursor.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. (function(){
  2. var Pos = CodeMirror.Pos;
  3. function SearchCursor(cm, query, pos, caseFold) {
  4. this.atOccurrence = false; this.cm = cm;
  5. if (caseFold == null && typeof query == "string") caseFold = false;
  6. pos = pos ? cm.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 = cm.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 + 1;
  25. }
  26. } else {
  27. query.lastIndex = pos.ch;
  28. var line = cm.getLine(pos.line), match = query.exec(line),
  29. start = match && match.index;
  30. }
  31. if (match && match[0])
  32. return {from: Pos(pos.line, start),
  33. to: Pos(pos.line, start + match[0].length),
  34. match: match};
  35. };
  36. } else { // String query
  37. if (caseFold) query = query.toLowerCase();
  38. var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
  39. var target = query.split("\n");
  40. // Different methods for single-line and multi-line queries
  41. if (target.length == 1) {
  42. if (!query.length) {
  43. // Empty string would match anything and never progress, so
  44. // we define it to match nothing instead.
  45. this.matches = function() {};
  46. } else {
  47. this.matches = function(reverse, pos) {
  48. var line = fold(cm.getLine(pos.line)), len = query.length, match;
  49. if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
  50. : (match = line.indexOf(query, pos.ch)) != -1)
  51. return {from: Pos(pos.line, match),
  52. to: Pos(pos.line, match + len)};
  53. };
  54. }
  55. } else {
  56. this.matches = function(reverse, pos) {
  57. var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln));
  58. var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
  59. if (reverse ? offsetA >= pos.ch || offsetA != match.length
  60. : offsetA <= pos.ch || offsetA != line.length - match.length)
  61. return;
  62. for (;;) {
  63. if (reverse ? !ln : ln == cm.lineCount() - 1) return;
  64. line = fold(cm.getLine(ln += reverse ? -1 : 1));
  65. match = target[reverse ? --idx : ++idx];
  66. if (idx > 0 && idx < target.length - 1) {
  67. if (line != match) return;
  68. else continue;
  69. }
  70. var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
  71. if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
  72. return;
  73. var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
  74. return {from: reverse ? end : start, to: reverse ? start : end};
  75. }
  76. };
  77. }
  78. }
  79. }
  80. SearchCursor.prototype = {
  81. findNext: function() {return this.find(false);},
  82. findPrevious: function() {return this.find(true);},
  83. find: function(reverse) {
  84. var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to);
  85. function savePosAndFail(line) {
  86. var pos = Pos(line, 0);
  87. self.pos = {from: pos, to: pos};
  88. self.atOccurrence = false;
  89. return false;
  90. }
  91. for (;;) {
  92. if (this.pos = this.matches(reverse, pos)) {
  93. if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
  94. this.atOccurrence = true;
  95. return this.pos.match || true;
  96. }
  97. if (reverse) {
  98. if (!pos.line) return savePosAndFail(0);
  99. pos = Pos(pos.line-1, this.cm.getLine(pos.line-1).length);
  100. }
  101. else {
  102. var maxLine = this.cm.lineCount();
  103. if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
  104. pos = Pos(pos.line + 1, 0);
  105. }
  106. }
  107. },
  108. from: function() {if (this.atOccurrence) return this.pos.from;},
  109. to: function() {if (this.atOccurrence) return this.pos.to;},
  110. replace: function(newText) {
  111. if (!this.atOccurrence) return;
  112. var lines = CodeMirror.splitLines(newText);
  113. this.cm.replaceRange(lines, this.pos.from, this.pos.to);
  114. this.pos.to = Pos(this.pos.from.line + lines.length - 1,
  115. lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
  116. }
  117. };
  118. CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
  119. return new SearchCursor(this, query, pos, caseFold);
  120. });
  121. })();