searchcursor.js 5.2 KB

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