1/* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS 17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. 20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29/** 30 * @constructor 31 * @implements {WebInspector.SearchScope} 32 */ 33WebInspector.SourcesSearchScope = function() 34{ 35 // FIXME: Add title once it is used by search controller. 36 this._searchId = 0; 37 this._workspace = WebInspector.workspace; 38} 39 40WebInspector.SourcesSearchScope.prototype = { 41 /** 42 * @param {!WebInspector.Progress} progress 43 * @param {function(boolean)} indexingFinishedCallback 44 */ 45 performIndexing: function(progress, indexingFinishedCallback) 46 { 47 this.stopSearch(); 48 49 var projects = this._projects(); 50 var compositeProgress = new WebInspector.CompositeProgress(progress); 51 progress.addEventListener(WebInspector.Progress.Events.Canceled, indexingCanceled); 52 for (var i = 0; i < projects.length; ++i) { 53 var project = projects[i]; 54 var projectProgress = compositeProgress.createSubProgress(project.uiSourceCodes().length); 55 project.indexContent(projectProgress); 56 } 57 compositeProgress.addEventListener(WebInspector.Progress.Events.Done, indexingFinishedCallback.bind(this, true)); 58 59 function indexingCanceled() 60 { 61 indexingFinishedCallback(false); 62 progress.done(); 63 } 64 }, 65 66 /** 67 * @return {!Array.<!WebInspector.Project>} 68 */ 69 _projects: function() 70 { 71 /** 72 * @param {!WebInspector.Project} project 73 * @return {boolean} 74 */ 75 function filterOutServiceProjects(project) 76 { 77 return !project.isServiceProject() || project.type() === WebInspector.projectTypes.Formatter; 78 } 79 80 /** 81 * @param {!WebInspector.Project} project 82 * @return {boolean} 83 */ 84 function filterOutContentScriptsIfNeeded(project) 85 { 86 return WebInspector.settings.searchInContentScripts.get() || project.type() !== WebInspector.projectTypes.ContentScripts; 87 } 88 89 return this._workspace.projects().filter(filterOutServiceProjects).filter(filterOutContentScriptsIfNeeded); 90 }, 91 92 /** 93 * @param {!WebInspector.ProjectSearchConfig} searchConfig 94 * @param {!WebInspector.Progress} progress 95 * @param {function(!WebInspector.FileBasedSearchResult)} searchResultCallback 96 * @param {function(boolean)} searchFinishedCallback 97 */ 98 performSearch: function(searchConfig, progress, searchResultCallback, searchFinishedCallback) 99 { 100 this.stopSearch(); 101 this._searchResultCallback = searchResultCallback; 102 this._searchFinishedCallback = searchFinishedCallback; 103 this._searchConfig = searchConfig; 104 105 var projects = this._projects(); 106 var barrier = new CallbackBarrier(); 107 var compositeProgress = new WebInspector.CompositeProgress(progress); 108 for (var i = 0; i < projects.length; ++i) { 109 var project = projects[i]; 110 var weight = project.uiSourceCodes().length; 111 var projectProgress = new WebInspector.CompositeProgress(compositeProgress.createSubProgress(weight)); 112 var findMatchingFilesProgress = projectProgress.createSubProgress(); 113 var searchContentProgress = projectProgress.createSubProgress(); 114 var barrierCallback = barrier.createCallback(); 115 var callback = this._processMatchingFilesForProject.bind(this, this._searchId, project, searchContentProgress, barrierCallback); 116 project.findFilesMatchingSearchRequest(searchConfig, findMatchingFilesProgress, callback); 117 } 118 barrier.callWhenDone(this._searchFinishedCallback.bind(this, true)); 119 }, 120 121 /** 122 * @param {number} searchId 123 * @param {!WebInspector.Project} project 124 * @param {!WebInspector.Progress} progress 125 * @param {function()} callback 126 * @param {!Array.<string>} files 127 */ 128 _processMatchingFilesForProject: function(searchId, project, progress, callback, files) 129 { 130 if (searchId !== this._searchId) { 131 this._searchFinishedCallback(false); 132 return; 133 } 134 135 addDirtyFiles.call(this); 136 137 if (!files.length) { 138 progress.done(); 139 callback(); 140 return; 141 } 142 143 progress.setTotalWork(files.length); 144 145 var fileIndex = 0; 146 var maxFileContentRequests = 20; 147 var callbacksLeft = 0; 148 149 for (var i = 0; i < maxFileContentRequests && i < files.length; ++i) 150 scheduleSearchInNextFileOrFinish.call(this); 151 152 /** 153 * @this {WebInspector.SourcesSearchScope} 154 */ 155 function addDirtyFiles() 156 { 157 var matchingFiles = StringSet.fromArray(files); 158 var uiSourceCodes = project.uiSourceCodes(); 159 for (var i = 0; i < uiSourceCodes.length; ++i) { 160 if (!uiSourceCodes[i].isDirty()) 161 continue; 162 var path = uiSourceCodes[i].path(); 163 if (!matchingFiles.contains(path) && this._searchConfig.filePathMatchesFileQuery(path)) 164 files.push(path); 165 } 166 } 167 168 /** 169 * @param {string} path 170 * @this {WebInspector.SourcesSearchScope} 171 */ 172 function searchInNextFile(path) 173 { 174 var uiSourceCode = project.uiSourceCode(path); 175 if (!uiSourceCode) { 176 --callbacksLeft; 177 progress.worked(1); 178 scheduleSearchInNextFileOrFinish.call(this); 179 return; 180 } 181 if (uiSourceCode.isDirty()) 182 contentLoaded.call(this, uiSourceCode.path(), uiSourceCode.workingCopy()); 183 else 184 uiSourceCode.checkContentUpdated(contentUpdated.bind(this, uiSourceCode)); 185 } 186 187 /** 188 * @param {!WebInspector.UISourceCode} uiSourceCode 189 * @this {WebInspector.SourcesSearchScope} 190 */ 191 function contentUpdated(uiSourceCode) 192 { 193 uiSourceCode.requestContent(contentLoaded.bind(this, uiSourceCode.path())); 194 } 195 196 /** 197 * @this {WebInspector.SourcesSearchScope} 198 */ 199 function scheduleSearchInNextFileOrFinish() 200 { 201 if (fileIndex >= files.length) { 202 if (!callbacksLeft) { 203 progress.done(); 204 callback(); 205 return; 206 } 207 return; 208 } 209 210 ++callbacksLeft; 211 var path = files[fileIndex++]; 212 setTimeout(searchInNextFile.bind(this, path), 0); 213 } 214 215 /** 216 * @param {string} path 217 * @param {?string} content 218 * @this {WebInspector.SourcesSearchScope} 219 */ 220 function contentLoaded(path, content) 221 { 222 /** 223 * @param {!WebInspector.ContentProvider.SearchMatch} a 224 * @param {!WebInspector.ContentProvider.SearchMatch} b 225 */ 226 function matchesComparator(a, b) 227 { 228 return a.lineNumber - b.lineNumber; 229 } 230 231 progress.worked(1); 232 var matches = []; 233 var queries = this._searchConfig.queries(); 234 if (content !== null) { 235 for (var i = 0; i < queries.length; ++i) { 236 var nextMatches = WebInspector.ContentProvider.performSearchInContent(content, queries[i], !this._searchConfig.ignoreCase(), this._searchConfig.isRegex()) 237 matches = matches.mergeOrdered(nextMatches, matchesComparator); 238 } 239 } 240 var uiSourceCode = project.uiSourceCode(path); 241 if (matches && uiSourceCode) { 242 var searchResult = new WebInspector.FileBasedSearchResult(uiSourceCode, matches); 243 this._searchResultCallback(searchResult); 244 } 245 246 --callbacksLeft; 247 scheduleSearchInNextFileOrFinish.call(this); 248 } 249 }, 250 251 stopSearch: function() 252 { 253 ++this._searchId; 254 }, 255 256 /** 257 * @param {!WebInspector.ProjectSearchConfig} searchConfig 258 * @return {!WebInspector.FileBasedSearchResultsPane} 259 */ 260 createSearchResultsPane: function(searchConfig) 261 { 262 return new WebInspector.FileBasedSearchResultsPane(searchConfig); 263 } 264} 265