1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/trace", 5 "firebug/lib/options", 6 ], 7 function(FBTrace, Options) { 8 9 // ********************************************************************************************* // 10 // Constants 11 12 var Ci = Components.interfaces; 13 var Cc = Components.classes; 14 var Cu = Components.utils; 15 16 var Search = {}; 17 18 var finder = Search.finder = Cc["@mozilla.org/embedcomp/rangefind;1"].createInstance(Ci.nsIFind); 19 20 // ********************************************************************************************* // 21 22 /** 23 * @class Searches for text in a given node. 24 * 25 * @constructor 26 * @param {Node} rootNode Node to search 27 * @param {function} rowFinder Results filter. On find this method will be called 28 * with the node containing the matched text as the first parameter. This may 29 * be undefined to return the node as is. 30 */ 31 Search.TextSearch = function(rootNode, rowFinder) 32 { 33 var doc = rootNode.ownerDocument; 34 var count, searchRange, startPt; 35 36 /** 37 * Find the first result in the node. 38 * 39 * @param {String} text Text to search for 40 * @param {boolean} reverse True to perform a reverse search 41 * @param {boolean} caseSensitive True to perform a case sensitive search 42 */ 43 this.find = function(text, reverse, caseSensitive) 44 { 45 this.text = text; 46 47 finder.findBackwards = !!reverse; 48 finder.caseSensitive = !!caseSensitive; 49 50 var range = this.range = finder.Find( 51 text, searchRange, 52 startPt || searchRange, 53 searchRange); 54 55 var match = range ? range.startContainer : null; 56 return this.currentNode = (rowFinder && match ? rowFinder(match) : match); 57 }; 58 59 /** 60 * Find the next search result 61 * 62 * @param {boolean} wrapAround True to wrap the search if the end of range is reached 63 * @param {boolean} sameNode True to return multiple results from the same text node 64 * @param {boolean} reverse True to search in reverse 65 * @param {boolean} caseSensitive True to perform a case sensitive search 66 */ 67 this.findNext = function(wrapAround, sameNode, reverse, caseSensitive) 68 { 69 this.wrapped = false; 70 startPt = undefined; 71 72 if (sameNode && this.range) 73 { 74 startPt = this.range.cloneRange(); 75 if (reverse) 76 startPt.setEnd(startPt.startContainer, startPt.startOffset); 77 else 78 startPt.setStart(startPt.startContainer, startPt.startOffset+1); 79 } 80 81 if (!startPt) 82 { 83 var curNode = this.currentNode ? this.currentNode : rootNode; 84 startPt = doc.createRange(); 85 try 86 { 87 if (reverse) 88 { 89 startPt.setStartBefore(curNode); 90 } 91 else 92 { 93 startPt.setStartAfter(curNode); 94 } 95 } 96 catch (e) 97 { 98 if (FBTrace.DBG_ERRORS) 99 FBTrace.sysout("lib.TextSearch.findNext setStartAfter fails for nodeType:"+ 100 (this.currentNode?this.currentNode.nodeType:rootNode.nodeType),e); 101 102 try 103 { 104 FBTrace.sysout("setStart try\n"); 105 startPt.setStart(curNode); 106 FBTrace.sysout("setStart success\n"); 107 } 108 catch (exc) 109 { 110 return; 111 } 112 } 113 } 114 115 var match = startPt && this.find(this.text, reverse, caseSensitive); 116 if (!match && wrapAround) 117 { 118 this.wrapped = true; 119 this.reset(); 120 return this.find(this.text, reverse, caseSensitive); 121 } 122 123 return match; 124 }; 125 126 /** 127 * Resets the instance state to the initial state. 128 */ 129 this.reset = function() 130 { 131 searchRange = doc.createRange(); 132 searchRange.selectNode(rootNode); 133 134 startPt = searchRange; 135 }; 136 137 this.reset(); 138 }; 139 140 Search.SourceBoxTextSearch = function(sourceBox) 141 { 142 this.tryToContinueSearch = function(sBox, text) 143 { 144 if (sBox != sourceBox) 145 return false; 146 147 var isSubstring = text.indexOf(this.text) !=-1 || this.text.indexOf(text) !=-1; 148 149 if (isSubstring && this.mark && Math.abs(sourceBox.centralLine - this.mark) < 10) 150 this.mark--; 151 else 152 this.reset(); 153 154 return true; 155 }; 156 this.find = function(text, reverse, caseSensitive) 157 { 158 this.text = text; 159 160 this.re = new Search.ReversibleRegExp(text); 161 162 return this.findNext(false, reverse, caseSensitive); 163 }; 164 165 this.findNext = function(wrapAround, reverse, caseSensitive) 166 { 167 this.wrapped = false; 168 var lines = sourceBox.lines; 169 var match = null; 170 for (var iter = new Search.ReversibleIterator(lines.length, this.mark, reverse); iter.next();) 171 { 172 match = this.re.exec(lines[iter.index], false, caseSensitive); 173 if (match) 174 { 175 this.mark = iter.index; 176 return iter.index; 177 } 178 } 179 180 if (!match && wrapAround) 181 { 182 this.reset(); 183 this.wrapped = true; 184 return this.findNext(false, reverse, caseSensitive); 185 } 186 187 return match; 188 }; 189 190 this.reset = function() 191 { 192 delete this.mark; 193 }; 194 195 this.reset(); 196 }; 197 198 Search.ReversibleIterator = function(length, start, reverse) 199 { 200 this.length = length; 201 this.index = start; 202 this.reversed = !!reverse; 203 204 this.next = function() 205 { 206 if (this.index === undefined || this.index === null) 207 { 208 this.index = this.reversed ? length : -1; 209 } 210 this.index += this.reversed ? -1 : 1; 211 212 return 0 <= this.index && this.index < length; 213 }; 214 215 this.reverse = function() 216 { 217 this.reversed = !this.reversed; 218 }; 219 }; 220 221 222 /** 223 * @class Implements a RegExp-like object that will search for the literal value 224 * of a given string, rather than the regular expression. This allows for 225 * iterative literal searches without having to escape user input strings 226 * to prevent invalid regular expressions from being used. 227 * 228 * @constructor 229 * @param {String} literal Text to search for 230 * @param {Boolean} reverse Truthy to preform a reverse search, falsy to perform a forward seach 231 * @param {Boolean} caseSensitive Truthy to perform a case sensitive search, falsy to perform a case insensitive search. 232 */ 233 Search.LiteralRegExp = function(literal, reverse, caseSensitive) 234 { 235 var searchToken = (!caseSensitive) ? literal.toLowerCase() : literal; 236 237 this.__defineGetter__("global", function() { return true; }); 238 this.__defineGetter__("multiline", function() { return true; }); 239 this.__defineGetter__("reverse", function() { return reverse; }); 240 this.__defineGetter__("ignoreCase", function() { return !caseSensitive; }); 241 this.lastIndex = 0; 242 243 this.exec = function(text) 244 { 245 if (!text) 246 return null; 247 248 var searchText = (!caseSensitive) ? text.toLowerCase() : text, 249 startIndex = (reverse ? text.length-1 : 0) + this.lastIndex, 250 index; 251 252 if (0 <= startIndex && startIndex < text.length) 253 index = searchText[reverse ? "lastIndexOf" : "indexOf"](searchToken, startIndex); 254 else 255 index = -1; 256 257 if (index >= 0) 258 { 259 var ret = [ text.substr(index, searchToken.length) ]; 260 ret.index = index; 261 ret.input = text; 262 this.lastIndex = index + (reverse ? -1*text.length : searchToken.length); 263 return ret; 264 } 265 else 266 this.lastIndex = 0; 267 268 return null; 269 }; 270 271 this.test = function(text) 272 { 273 if (!text) 274 return false; 275 276 var searchText = (!caseSensitive) ? text.toLowerCase() : text; 277 return searchText.indexOf(searchToken) >= 0; 278 }; 279 }; 280 281 Search.ReversibleRegExp = function(regex, flags) 282 { 283 var re = {}; 284 285 function expression(text, reverse) 286 { 287 return text + (reverse ? "(?![\\s\\S]*" + text + ")" : ""); 288 } 289 290 function flag(flags, caseSensitive) 291 { 292 return (flags || "") + (caseSensitive ? "" : "i"); 293 } 294 295 this.exec = function(text, reverse, caseSensitive, lastMatch) 296 { 297 // Ensure we have a regex 298 var key = (reverse ? "r" : "n") + (caseSensitive ? "n" : "i") 299 + (Firebug.searchUseRegularExpression ? "r" : "n"); 300 if (!re[key]) 301 { 302 try 303 { 304 if (Firebug.searchUseRegularExpression) 305 re[key] = new RegExp(expression(regex, reverse), flag(flags, caseSensitive)); 306 else 307 re[key] = new Search.LiteralRegExp(regex, reverse, caseSensitive); 308 } 309 catch (ex) 310 { 311 // The user likely entered an invalid regular expression or is in the 312 // process of entering a valid one. Treat this as a plain text search 313 re[key] = new Search.LiteralRegExp(regex, reverse, caseSensitive); 314 } 315 } 316 317 // Modify as needed to all for iterative searches 318 var indexOffset = 0; 319 var searchText = text; 320 if (lastMatch) 321 { 322 if (reverse) 323 { 324 searchText = text.substr(0, lastMatch.index); 325 } 326 else 327 { 328 indexOffset = lastMatch.index+lastMatch[0].length; 329 searchText = text.substr(indexOffset); 330 } 331 } 332 333 var curRe = re[key]; 334 curRe.lastIndex = 0; 335 var ret = curRe.exec(searchText); 336 if (ret) 337 { 338 ret.input = text; 339 ret.index = ret.index + indexOffset; 340 ret.reverse = reverse; 341 ret.caseSensitive = caseSensitive; 342 } 343 return ret; 344 }; 345 }; 346 347 return Search; 348 349 // ********************************************************************************************* // 350 }); 351