1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/sourceeditor/codemirror/search/searchcursor.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,186 @@ 1.4 +(function(mod) { 1.5 + if (typeof exports == "object" && typeof module == "object") // CommonJS 1.6 + mod(require("../../lib/codemirror")); 1.7 + else if (typeof define == "function" && define.amd) // AMD 1.8 + define(["../../lib/codemirror"], mod); 1.9 + else // Plain browser env 1.10 + mod(CodeMirror); 1.11 +})(function(CodeMirror) { 1.12 + "use strict"; 1.13 + var Pos = CodeMirror.Pos; 1.14 + 1.15 + function SearchCursor(doc, query, pos, caseFold) { 1.16 + this.atOccurrence = false; this.doc = doc; 1.17 + if (caseFold == null && typeof query == "string") caseFold = false; 1.18 + 1.19 + pos = pos ? doc.clipPos(pos) : Pos(0, 0); 1.20 + this.pos = {from: pos, to: pos}; 1.21 + 1.22 + // The matches method is filled in based on the type of query. 1.23 + // It takes a position and a direction, and returns an object 1.24 + // describing the next occurrence of the query, or null if no 1.25 + // more matches were found. 1.26 + if (typeof query != "string") { // Regexp match 1.27 + if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); 1.28 + this.matches = function(reverse, pos) { 1.29 + if (reverse) { 1.30 + query.lastIndex = 0; 1.31 + var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; 1.32 + for (;;) { 1.33 + query.lastIndex = cutOff; 1.34 + var newMatch = query.exec(line); 1.35 + if (!newMatch) break; 1.36 + match = newMatch; 1.37 + start = match.index; 1.38 + cutOff = match.index + (match[0].length || 1); 1.39 + if (cutOff == line.length) break; 1.40 + } 1.41 + var matchLen = (match && match[0].length) || 0; 1.42 + if (!matchLen) { 1.43 + if (start == 0 && line.length == 0) {match = undefined;} 1.44 + else if (start != doc.getLine(pos.line).length) { 1.45 + matchLen++; 1.46 + } 1.47 + } 1.48 + } else { 1.49 + query.lastIndex = pos.ch; 1.50 + var line = doc.getLine(pos.line), match = query.exec(line); 1.51 + var matchLen = (match && match[0].length) || 0; 1.52 + var start = match && match.index; 1.53 + if (start + matchLen != line.length && !matchLen) matchLen = 1; 1.54 + } 1.55 + if (match && matchLen) 1.56 + return {from: Pos(pos.line, start), 1.57 + to: Pos(pos.line, start + matchLen), 1.58 + match: match}; 1.59 + }; 1.60 + } else { // String query 1.61 + var origQuery = query; 1.62 + if (caseFold) query = query.toLowerCase(); 1.63 + var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; 1.64 + var target = query.split("\n"); 1.65 + // Different methods for single-line and multi-line queries 1.66 + if (target.length == 1) { 1.67 + if (!query.length) { 1.68 + // Empty string would match anything and never progress, so 1.69 + // we define it to match nothing instead. 1.70 + this.matches = function() {}; 1.71 + } else { 1.72 + this.matches = function(reverse, pos) { 1.73 + if (reverse) { 1.74 + var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); 1.75 + var match = line.lastIndexOf(query); 1.76 + if (match > -1) { 1.77 + match = adjustPos(orig, line, match); 1.78 + return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; 1.79 + } 1.80 + } else { 1.81 + var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); 1.82 + var match = line.indexOf(query); 1.83 + if (match > -1) { 1.84 + match = adjustPos(orig, line, match) + pos.ch; 1.85 + return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; 1.86 + } 1.87 + } 1.88 + }; 1.89 + } 1.90 + } else { 1.91 + var origTarget = origQuery.split("\n"); 1.92 + this.matches = function(reverse, pos) { 1.93 + var last = target.length - 1; 1.94 + if (reverse) { 1.95 + if (pos.line - (target.length - 1) < doc.firstLine()) return; 1.96 + if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return; 1.97 + var to = Pos(pos.line, origTarget[last].length); 1.98 + for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln) 1.99 + if (target[i] != fold(doc.getLine(ln))) return; 1.100 + var line = doc.getLine(ln), cut = line.length - origTarget[0].length; 1.101 + if (fold(line.slice(cut)) != target[0]) return; 1.102 + return {from: Pos(ln, cut), to: to}; 1.103 + } else { 1.104 + if (pos.line + (target.length - 1) > doc.lastLine()) return; 1.105 + var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length; 1.106 + if (fold(line.slice(cut)) != target[0]) return; 1.107 + var from = Pos(pos.line, cut); 1.108 + for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) 1.109 + if (target[i] != fold(doc.getLine(ln))) return; 1.110 + if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return; 1.111 + return {from: from, to: Pos(ln, origTarget[last].length)}; 1.112 + } 1.113 + }; 1.114 + } 1.115 + } 1.116 + } 1.117 + 1.118 + SearchCursor.prototype = { 1.119 + findNext: function() {return this.find(false);}, 1.120 + findPrevious: function() {return this.find(true);}, 1.121 + 1.122 + find: function(reverse) { 1.123 + var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); 1.124 + function savePosAndFail(line) { 1.125 + var pos = Pos(line, 0); 1.126 + self.pos = {from: pos, to: pos}; 1.127 + self.atOccurrence = false; 1.128 + return false; 1.129 + } 1.130 + 1.131 + for (;;) { 1.132 + if (this.pos = this.matches(reverse, pos)) { 1.133 + this.atOccurrence = true; 1.134 + return this.pos.match || true; 1.135 + } 1.136 + if (reverse) { 1.137 + if (!pos.line) return savePosAndFail(0); 1.138 + pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); 1.139 + } 1.140 + else { 1.141 + var maxLine = this.doc.lineCount(); 1.142 + if (pos.line == maxLine - 1) return savePosAndFail(maxLine); 1.143 + pos = Pos(pos.line + 1, 0); 1.144 + } 1.145 + } 1.146 + }, 1.147 + 1.148 + from: function() {if (this.atOccurrence) return this.pos.from;}, 1.149 + to: function() {if (this.atOccurrence) return this.pos.to;}, 1.150 + 1.151 + replace: function(newText) { 1.152 + if (!this.atOccurrence) return; 1.153 + var lines = CodeMirror.splitLines(newText); 1.154 + this.doc.replaceRange(lines, this.pos.from, this.pos.to); 1.155 + this.pos.to = Pos(this.pos.from.line + lines.length - 1, 1.156 + lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); 1.157 + } 1.158 + }; 1.159 + 1.160 + // Maps a position in a case-folded line back to a position in the original line 1.161 + // (compensating for codepoints increasing in number during folding) 1.162 + function adjustPos(orig, folded, pos) { 1.163 + if (orig.length == folded.length) return pos; 1.164 + for (var pos1 = Math.min(pos, orig.length);;) { 1.165 + var len1 = orig.slice(0, pos1).toLowerCase().length; 1.166 + if (len1 < pos) ++pos1; 1.167 + else if (len1 > pos) --pos1; 1.168 + else return pos1; 1.169 + } 1.170 + } 1.171 + 1.172 + CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { 1.173 + return new SearchCursor(this.doc, query, pos, caseFold); 1.174 + }); 1.175 + CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { 1.176 + return new SearchCursor(this, query, pos, caseFold); 1.177 + }); 1.178 + 1.179 + CodeMirror.defineExtension("selectMatches", function(query, caseFold) { 1.180 + var ranges = [], next; 1.181 + var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold); 1.182 + while (next = cur.findNext()) { 1.183 + if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break; 1.184 + ranges.push({anchor: cur.from(), head: cur.to()}); 1.185 + } 1.186 + if (ranges.length) 1.187 + this.setSelections(ranges, 0); 1.188 + }); 1.189 +});