Home | History | Annotate | Download | only in search
      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  */
     33 WebInspector.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 
     40 WebInspector.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