Home | History | Annotate | Download | only in sources
      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 }
     38 
     39 /**
     40  * @param {!WebInspector.UISourceCode} uiSourceCode1
     41  * @param {!WebInspector.UISourceCode} uiSourceCode2
     42  * @return {number}
     43  */
     44 WebInspector.SourcesSearchScope._filesComparator = function(uiSourceCode1, uiSourceCode2)
     45 {
     46     if (uiSourceCode1.isDirty() && !uiSourceCode2.isDirty())
     47         return -1;
     48     if (!uiSourceCode1.isDirty() && uiSourceCode2.isDirty())
     49         return 1;
     50     if (uiSourceCode1.url && !uiSourceCode2.url)
     51         return -1;
     52     if (!uiSourceCode1.url && uiSourceCode2.url)
     53         return 1;
     54     return String.naturalOrderComparator(uiSourceCode1.fullDisplayName(), uiSourceCode2.fullDisplayName());
     55 }
     56 
     57 
     58 WebInspector.SourcesSearchScope.prototype = {
     59     /**
     60      * @param {!WebInspector.Progress} progress
     61      * @param {function(boolean)} indexingFinishedCallback
     62      */
     63     performIndexing: function(progress, indexingFinishedCallback)
     64     {
     65         this.stopSearch();
     66 
     67         var projects = this._projects();
     68         var compositeProgress = new WebInspector.CompositeProgress(progress);
     69         progress.addEventListener(WebInspector.Progress.Events.Canceled, indexingCanceled);
     70         for (var i = 0; i < projects.length; ++i) {
     71             var project = projects[i];
     72             var projectProgress = compositeProgress.createSubProgress(project.uiSourceCodes().length);
     73             project.indexContent(projectProgress);
     74         }
     75         compositeProgress.addEventListener(WebInspector.Progress.Events.Done, indexingFinishedCallback.bind(this, true));
     76 
     77         function indexingCanceled()
     78         {
     79             indexingFinishedCallback(false);
     80             progress.done();
     81         }
     82     },
     83 
     84     /**
     85      * @return {!Array.<!WebInspector.Project>}
     86      */
     87     _projects: function()
     88     {
     89         /**
     90          * @param {!WebInspector.Project} project
     91          * @return {boolean}
     92          */
     93         function filterOutServiceProjects(project)
     94         {
     95             return !project.isServiceProject() || project.type() === WebInspector.projectTypes.Formatter;
     96         }
     97 
     98         /**
     99          * @param {!WebInspector.Project} project
    100          * @return {boolean}
    101          */
    102         function filterOutContentScriptsIfNeeded(project)
    103         {
    104             return WebInspector.settings.searchInContentScripts.get() || project.type() !== WebInspector.projectTypes.ContentScripts;
    105         }
    106 
    107         return WebInspector.workspace.projects().filter(filterOutServiceProjects).filter(filterOutContentScriptsIfNeeded);
    108     },
    109 
    110     /**
    111      * @param {!WebInspector.ProjectSearchConfig} searchConfig
    112      * @param {!WebInspector.Progress} progress
    113      * @param {function(!WebInspector.FileBasedSearchResult)} searchResultCallback
    114      * @param {function(boolean)} searchFinishedCallback
    115      */
    116     performSearch: function(searchConfig, progress, searchResultCallback, searchFinishedCallback)
    117     {
    118         this.stopSearch();
    119         this._searchResultCandidates = [];
    120         this._searchResultCallback = searchResultCallback;
    121         this._searchFinishedCallback = searchFinishedCallback;
    122         this._searchConfig = searchConfig;
    123 
    124         var projects = this._projects();
    125         var barrier = new CallbackBarrier();
    126         var compositeProgress = new WebInspector.CompositeProgress(progress);
    127         var searchContentProgress = compositeProgress.createSubProgress();
    128         var findMatchingFilesProgress = new WebInspector.CompositeProgress(compositeProgress.createSubProgress());
    129         for (var i = 0; i < projects.length; ++i) {
    130             var project = projects[i];
    131             var weight = project.uiSourceCodes().length;
    132             var findMatchingFilesInProjectProgress = findMatchingFilesProgress.createSubProgress(weight);
    133             var barrierCallback = barrier.createCallback();
    134             var filesMathingFileQuery = this._projectFilesMatchingFileQuery(project, searchConfig);
    135             var callback = this._processMatchingFilesForProject.bind(this, this._searchId, project, filesMathingFileQuery, barrierCallback);
    136             project.findFilesMatchingSearchRequest(searchConfig, filesMathingFileQuery, findMatchingFilesInProjectProgress, callback);
    137         }
    138         barrier.callWhenDone(this._processMatchingFiles.bind(this, this._searchId, searchContentProgress, this._searchFinishedCallback.bind(this, true)));
    139     },
    140 
    141     /**
    142      * @param {!WebInspector.Project} project
    143      * @param {!WebInspector.ProjectSearchConfig} searchConfig
    144      * @param {boolean=} dirtyOnly
    145      * @return {!Array.<string>}
    146      */
    147     _projectFilesMatchingFileQuery: function(project, searchConfig, dirtyOnly)
    148     {
    149         var result = [];
    150         var uiSourceCodes = project.uiSourceCodes();
    151         for (var i = 0; i < uiSourceCodes.length; ++i) {
    152             var uiSourceCode = uiSourceCodes[i];
    153             if (dirtyOnly && !uiSourceCode.isDirty())
    154                 continue;
    155             if (this._searchConfig.filePathMatchesFileQuery(uiSourceCode.fullDisplayName()))
    156                 result.push(uiSourceCode.path());
    157         }
    158         result.sort(String.naturalOrderComparator);
    159         return result;
    160     },
    161 
    162     /**
    163      * @param {number} searchId
    164      * @param {!WebInspector.Project} project
    165      * @param {!Array.<string>} filesMathingFileQuery
    166      * @param {function()} callback
    167      * @param {!Array.<string>} files
    168      */
    169     _processMatchingFilesForProject: function(searchId, project, filesMathingFileQuery, callback, files)
    170     {
    171         if (searchId !== this._searchId) {
    172             this._searchFinishedCallback(false);
    173             return;
    174         }
    175 
    176         files.sort(String.naturalOrderComparator);
    177         files = files.intersectOrdered(filesMathingFileQuery, String.naturalOrderComparator);
    178         var dirtyFiles = this._projectFilesMatchingFileQuery(project, this._searchConfig, true);
    179         files = files.mergeOrdered(dirtyFiles, String.naturalOrderComparator);
    180 
    181         var uiSourceCodes = [];
    182         for (var i = 0; i < files.length; ++i) {
    183             var uiSourceCode = project.uiSourceCode(files[i]);
    184             if (uiSourceCode)
    185                 uiSourceCodes.push(uiSourceCode);
    186         }
    187         uiSourceCodes.sort(WebInspector.SourcesSearchScope._filesComparator);
    188         this._searchResultCandidates = this._searchResultCandidates.mergeOrdered(uiSourceCodes, WebInspector.SourcesSearchScope._filesComparator);
    189         callback();
    190     },
    191 
    192     /**
    193      * @param {number} searchId
    194      * @param {!WebInspector.Progress} progress
    195      * @param {function()} callback
    196      */
    197     _processMatchingFiles: function(searchId, progress, callback)
    198     {
    199         if (searchId !== this._searchId) {
    200             this._searchFinishedCallback(false);
    201             return;
    202         }
    203 
    204         var files = this._searchResultCandidates;
    205         if (!files.length) {
    206             progress.done();
    207             callback();
    208             return;
    209         }
    210 
    211         progress.setTotalWork(files.length);
    212 
    213         var fileIndex = 0;
    214         var maxFileContentRequests = 20;
    215         var callbacksLeft = 0;
    216 
    217         for (var i = 0; i < maxFileContentRequests && i < files.length; ++i)
    218             scheduleSearchInNextFileOrFinish.call(this);
    219 
    220         /**
    221          * @param {!WebInspector.UISourceCode} uiSourceCode
    222          * @this {WebInspector.SourcesSearchScope}
    223          */
    224         function searchInNextFile(uiSourceCode)
    225         {
    226             if (uiSourceCode.isDirty())
    227                 contentLoaded.call(this, uiSourceCode, uiSourceCode.workingCopy());
    228             else
    229                 uiSourceCode.checkContentUpdated(contentUpdated.bind(this, uiSourceCode));
    230         }
    231 
    232         /**
    233          * @param {!WebInspector.UISourceCode} uiSourceCode
    234          * @this {WebInspector.SourcesSearchScope}
    235          */
    236         function contentUpdated(uiSourceCode)
    237         {
    238             uiSourceCode.requestContent(contentLoaded.bind(this, uiSourceCode));
    239         }
    240 
    241         /**
    242          * @this {WebInspector.SourcesSearchScope}
    243          */
    244         function scheduleSearchInNextFileOrFinish()
    245         {
    246             if (fileIndex >= files.length) {
    247                 if (!callbacksLeft) {
    248                     progress.done();
    249                     callback();
    250                     return;
    251                 }
    252                 return;
    253             }
    254 
    255             ++callbacksLeft;
    256             var uiSourceCode = files[fileIndex++];
    257             setTimeout(searchInNextFile.bind(this, uiSourceCode), 0);
    258         }
    259 
    260         /**
    261          * @param {!WebInspector.UISourceCode} uiSourceCode
    262          * @param {?string} content
    263          * @this {WebInspector.SourcesSearchScope}
    264          */
    265         function contentLoaded(uiSourceCode, content)
    266         {
    267             /**
    268              * @param {!WebInspector.ContentProvider.SearchMatch} a
    269              * @param {!WebInspector.ContentProvider.SearchMatch} b
    270              */
    271             function matchesComparator(a, b)
    272             {
    273                 return a.lineNumber - b.lineNumber;
    274             }
    275 
    276             progress.worked(1);
    277             var matches = [];
    278             var queries = this._searchConfig.queries();
    279             if (content !== null) {
    280                 for (var i = 0; i < queries.length; ++i) {
    281                     var nextMatches = WebInspector.ContentProvider.performSearchInContent(content, queries[i], !this._searchConfig.ignoreCase(), this._searchConfig.isRegex())
    282                     matches = matches.mergeOrdered(nextMatches, matchesComparator);
    283                 }
    284             }
    285             if (matches) {
    286                 var searchResult = new WebInspector.FileBasedSearchResult(uiSourceCode, matches);
    287                 this._searchResultCallback(searchResult);
    288             }
    289 
    290             --callbacksLeft;
    291             scheduleSearchInNextFileOrFinish.call(this);
    292         }
    293     },
    294 
    295     stopSearch: function()
    296     {
    297         ++this._searchId;
    298     },
    299 
    300     /**
    301      * @param {!WebInspector.ProjectSearchConfig} searchConfig
    302      * @return {!WebInspector.FileBasedSearchResultsPane}
    303      */
    304     createSearchResultsPane: function(searchConfig)
    305     {
    306         return new WebInspector.FileBasedSearchResultsPane(searchConfig);
    307     }
    308 }
    309