Home | History | Annotate | Download | only in search
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @constructor
      7  * @extends {WebInspector.VBox}
      8  */
      9 WebInspector.AdvancedSearchView = function()
     10 {
     11     WebInspector.VBox.call(this);
     12 
     13     this._searchId = 0;
     14 
     15     this.element.classList.add("search-view");
     16 
     17     this._searchPanelElement = this.element.createChild("div", "search-drawer-header");
     18     this._searchPanelElement.addEventListener("keydown", this._onKeyDown.bind(this), false);
     19 
     20     this._searchResultsElement = this.element.createChild("div");
     21     this._searchResultsElement.className = "search-results";
     22 
     23     this._search = this._searchPanelElement.createChild("input");
     24     this._search.placeholder = WebInspector.UIString("Search sources");
     25     this._search.setAttribute("type", "text");
     26     this._search.classList.add("search-config-search");
     27     this._search.setAttribute("results", "0");
     28     this._search.setAttribute("size", 30);
     29 
     30     this._ignoreCaseLabel = this._searchPanelElement.createChild("label");
     31     this._ignoreCaseLabel.classList.add("search-config-label");
     32     this._ignoreCaseCheckbox = this._ignoreCaseLabel.createChild("input");
     33     this._ignoreCaseCheckbox.setAttribute("type", "checkbox");
     34     this._ignoreCaseCheckbox.classList.add("search-config-checkbox");
     35     this._ignoreCaseLabel.appendChild(document.createTextNode(WebInspector.UIString("Ignore case")));
     36 
     37     this._regexLabel = this._searchPanelElement.createChild("label");
     38     this._regexLabel.classList.add("search-config-label");
     39     this._regexCheckbox = this._regexLabel.createChild("input");
     40     this._regexCheckbox.setAttribute("type", "checkbox");
     41     this._regexCheckbox.classList.add("search-config-checkbox");
     42     this._regexLabel.appendChild(document.createTextNode(WebInspector.UIString("Regular expression")));
     43 
     44     this._searchStatusBarElement = this.element.createChild("div", "search-status-bar-summary");
     45     this._searchMessageElement = this._searchStatusBarElement.createChild("span");
     46     this._searchResultsMessageElement = document.createElement("span");
     47 
     48     WebInspector.settings.advancedSearchConfig = WebInspector.settings.createSetting("advancedSearchConfig", new WebInspector.SearchConfig("", true, false).toPlainObject());
     49     this._load();
     50 }
     51 
     52 WebInspector.AdvancedSearchView.prototype = {
     53     /**
     54      * @return {!WebInspector.SearchConfig}
     55      */
     56     _buildSearchConfig: function()
     57     {
     58         return new WebInspector.SearchConfig(this._search.value, this._ignoreCaseCheckbox.checked, this._regexCheckbox.checked);
     59     },
     60 
     61     toggle: function()
     62     {
     63         var selection = window.getSelection();
     64         var queryCandidate;
     65         if (selection.rangeCount)
     66             queryCandidate = selection.toString().replace(/\r?\n.*/, "");
     67 
     68         if (!this.isShowing())
     69             WebInspector.inspectorView.showViewInDrawer("search");
     70         if (queryCandidate)
     71             this._search.value = queryCandidate;
     72         this.focus();
     73 
     74         this._startIndexing();
     75     },
     76 
     77     /**
     78      * @param {boolean} finished
     79      */
     80     _onIndexingFinished: function(finished)
     81     {
     82         delete this._isIndexing;
     83         this._indexingFinished(finished);
     84         if (!finished)
     85             delete this._pendingSearchConfig;
     86         if (!this._pendingSearchConfig)
     87             return;
     88         var searchConfig = this._pendingSearchConfig
     89         delete this._pendingSearchConfig;
     90         this._innerStartSearch(searchConfig);
     91     },
     92 
     93     _startIndexing: function()
     94     {
     95         this._isIndexing = true;
     96         // FIXME: this._currentSearchScope should be initialized based on searchConfig
     97         this._currentSearchScope = this._searchScopes()[0];
     98         if (this._progressIndicator)
     99             this._progressIndicator.done();
    100         this._progressIndicator = new WebInspector.ProgressIndicator();
    101         this._indexingStarted(this._progressIndicator);
    102         this._currentSearchScope.performIndexing(this._progressIndicator, this._onIndexingFinished.bind(this));
    103     },
    104 
    105     /**
    106      * @param {number} searchId
    107      * @param {!WebInspector.FileBasedSearchResult} searchResult
    108      */
    109     _onSearchResult: function(searchId, searchResult)
    110     {
    111         if (searchId !== this._searchId)
    112             return;
    113         this._addSearchResult(searchResult);
    114         if (!searchResult.searchMatches.length)
    115             return;
    116         if (!this._searchResultsPane)
    117             this._searchResultsPane = this._currentSearchScope.createSearchResultsPane(this._searchConfig);
    118         this._resetResults();
    119         this._searchResultsElement.appendChild(this._searchResultsPane.element);
    120         this._searchResultsPane.addSearchResult(searchResult);
    121     },
    122 
    123     /**
    124      * @param {number} searchId
    125      * @param {boolean} finished
    126      */
    127     _onSearchFinished: function(searchId, finished)
    128     {
    129         if (searchId !== this._searchId)
    130             return;
    131         if (!this._searchResultsPane)
    132             this._nothingFound();
    133         this._searchFinished(finished);
    134         delete this._searchConfig;
    135     },
    136 
    137     /**
    138      * @param {!WebInspector.SearchConfig} searchConfig
    139      */
    140     _startSearch: function(searchConfig)
    141     {
    142         this._resetSearch();
    143         ++this._searchId;
    144         if (!this._isIndexing)
    145             this._startIndexing();
    146         this._pendingSearchConfig = searchConfig;
    147     },
    148 
    149     /**
    150      * @param {!WebInspector.SearchConfig} searchConfig
    151      */
    152     _innerStartSearch: function(searchConfig)
    153     {
    154         this._searchConfig = searchConfig;
    155         // FIXME: this._currentSearchScope should be initialized based on searchConfig
    156         this._currentSearchScope = this._searchScopes()[0];
    157 
    158         if (this._progressIndicator)
    159             this._progressIndicator.done();
    160         this._progressIndicator = new WebInspector.ProgressIndicator();
    161         this._searchStarted(this._progressIndicator);
    162         this._currentSearchScope.performSearch(searchConfig, this._progressIndicator, this._onSearchResult.bind(this, this._searchId), this._onSearchFinished.bind(this, this._searchId));
    163     },
    164 
    165     _resetSearch: function()
    166     {
    167         this._stopSearch();
    168 
    169         if (this._searchResultsPane) {
    170             this._resetResults();
    171             delete this._searchResultsPane;
    172         }
    173     },
    174 
    175     _stopSearch: function()
    176     {
    177         if (this._progressIndicator)
    178             this._progressIndicator.cancel();
    179         if (this._currentSearchScope)
    180             this._currentSearchScope.stopSearch();
    181         delete this._searchConfig;
    182     },
    183 
    184     /**
    185      * @return {!Array.<!WebInspector.SearchScope>}
    186      */
    187     _searchScopes: function()
    188     {
    189         // FIXME: implement multiple search scopes.
    190         return /** @type {!Array.<!WebInspector.SearchScope>} */ (WebInspector.moduleManager.instances(WebInspector.SearchScope));
    191     },
    192 
    193     /**
    194      * @param {!WebInspector.ProgressIndicator} progressIndicator
    195      */
    196     _searchStarted: function(progressIndicator)
    197     {
    198         this._resetResults();
    199         this._resetCounters();
    200 
    201         this._searchMessageElement.textContent = WebInspector.UIString("Searching...");
    202         progressIndicator.show(this._searchStatusBarElement);
    203         this._updateSearchResultsMessage();
    204 
    205         if (!this._searchingView)
    206             this._searchingView = new WebInspector.EmptyView(WebInspector.UIString("Searching..."));
    207         this._searchingView.show(this._searchResultsElement);
    208     },
    209 
    210     /**
    211      * @param {!WebInspector.ProgressIndicator} progressIndicator
    212      */
    213     _indexingStarted: function(progressIndicator)
    214     {
    215         this._searchMessageElement.textContent = WebInspector.UIString("Indexing...");
    216         progressIndicator.show(this._searchStatusBarElement);
    217     },
    218 
    219     /**
    220      * @param {boolean} finished
    221      */
    222     _indexingFinished: function(finished)
    223     {
    224         this._searchMessageElement.textContent = finished ? "" : WebInspector.UIString("Indexing interrupted.");
    225     },
    226 
    227     _updateSearchResultsMessage: function()
    228     {
    229         if (this._searchMatchesCount && this._searchResultsCount)
    230             this._searchResultsMessageElement.textContent = WebInspector.UIString("Found %d matches in %d files.", this._searchMatchesCount, this._nonEmptySearchResultsCount);
    231         else
    232             this._searchResultsMessageElement.textContent = "";
    233     },
    234 
    235     _resetResults: function()
    236     {
    237         if (this._searchingView)
    238             this._searchingView.detach();
    239         if (this._notFoundView)
    240             this._notFoundView.detach();
    241         this._searchResultsElement.removeChildren();
    242     },
    243 
    244     _resetCounters: function()
    245     {
    246         this._searchMatchesCount = 0;
    247         this._searchResultsCount = 0;
    248         this._nonEmptySearchResultsCount = 0;
    249     },
    250 
    251     _nothingFound: function()
    252     {
    253         this._resetResults();
    254 
    255         if (!this._notFoundView)
    256             this._notFoundView = new WebInspector.EmptyView(WebInspector.UIString("No matches found."));
    257         this._notFoundView.show(this._searchResultsElement);
    258         this._searchResultsMessageElement.textContent = WebInspector.UIString("No matches found.");
    259     },
    260 
    261     /**
    262      * @param {!WebInspector.FileBasedSearchResult} searchResult
    263      */
    264     _addSearchResult: function(searchResult)
    265     {
    266         this._searchMatchesCount += searchResult.searchMatches.length;
    267         this._searchResultsCount++;
    268         if (searchResult.searchMatches.length)
    269             this._nonEmptySearchResultsCount++;
    270         this._updateSearchResultsMessage();
    271     },
    272 
    273     /**
    274      * @param {boolean} finished
    275      */
    276     _searchFinished: function(finished)
    277     {
    278         this._searchMessageElement.textContent = finished ? WebInspector.UIString("Search finished.") : WebInspector.UIString("Search interrupted.");
    279     },
    280 
    281     focus: function()
    282     {
    283         WebInspector.setCurrentFocusElement(this._search);
    284         this._search.select();
    285     },
    286 
    287     willHide: function()
    288     {
    289         this._stopSearch();
    290     },
    291 
    292     /**
    293      * @param {?Event} event
    294      */
    295     _onKeyDown: function(event)
    296     {
    297         switch (event.keyCode) {
    298         case WebInspector.KeyboardShortcut.Keys.Enter.code:
    299             this._onAction();
    300             break;
    301         }
    302     },
    303 
    304     _save: function()
    305     {
    306         WebInspector.settings.advancedSearchConfig.set(this._buildSearchConfig().toPlainObject());
    307     },
    308 
    309     _load: function()
    310     {
    311         var searchConfig = WebInspector.SearchConfig.fromPlainObject(WebInspector.settings.advancedSearchConfig.get());
    312         this._search.value = searchConfig.query();
    313         this._ignoreCaseCheckbox.checked = searchConfig.ignoreCase();
    314         this._regexCheckbox.checked = searchConfig.isRegex();
    315     },
    316 
    317     _onAction: function()
    318     {
    319         var searchConfig = this._buildSearchConfig();
    320         if (!searchConfig.query() || !searchConfig.query().length)
    321             return;
    322 
    323         this._save();
    324         this._startSearch(searchConfig);
    325     },
    326 
    327     __proto__: WebInspector.VBox.prototype
    328 }
    329 
    330 /**
    331  * @constructor
    332  * @param {!WebInspector.ProjectSearchConfig} searchConfig
    333  */
    334 WebInspector.SearchResultsPane = function(searchConfig)
    335 {
    336     this._searchConfig = searchConfig;
    337     this.element = document.createElement("div");
    338 }
    339 
    340 WebInspector.SearchResultsPane.prototype = {
    341     /**
    342      * @return {!WebInspector.ProjectSearchConfig}
    343      */
    344     get searchConfig()
    345     {
    346         return this._searchConfig;
    347     },
    348 
    349     /**
    350      * @param {!WebInspector.FileBasedSearchResult} searchResult
    351      */
    352     addSearchResult: function(searchResult) { }
    353 }
    354 
    355 /**
    356  * @constructor
    357  * @implements {WebInspector.ActionDelegate}
    358  */
    359 WebInspector.AdvancedSearchView.ToggleDrawerViewActionDelegate = function()
    360 {
    361 }
    362 
    363 WebInspector.AdvancedSearchView.ToggleDrawerViewActionDelegate.prototype = {
    364     /**
    365      * @return {boolean}
    366      */
    367     handleAction: function()
    368     {
    369         var searchView = this._searchView();
    370         if (!searchView)
    371             return false;
    372         if (!searchView.isShowing() || searchView._search !== document.activeElement) {
    373             WebInspector.inspectorView.showPanel("sources");
    374             searchView.toggle();
    375         } else {
    376             WebInspector.inspectorView.closeDrawer();
    377         }
    378         return true;
    379     },
    380 
    381     /**
    382      * @return {?WebInspector.AdvancedSearchView}
    383      */
    384     _searchView: function()
    385     {
    386         if (!this._view) {
    387             var extensions = WebInspector.moduleManager.extensions("drawer-view");
    388             for (var i = 0; i < extensions.length; ++i) {
    389                 if (extensions[i].descriptor()["name"] === "search") {
    390                     this._view = extensions[i].instance();
    391                     break;
    392                 }
    393             }
    394         }
    395         return this._view;
    396     }
    397 }
    398 
    399 
    400 /**
    401  * @constructor
    402  * @param {!WebInspector.UISourceCode} uiSourceCode
    403  * @param {!Array.<!Object>} searchMatches
    404  */
    405 WebInspector.FileBasedSearchResult = function(uiSourceCode, searchMatches) {
    406     this.uiSourceCode = uiSourceCode;
    407     this.searchMatches = searchMatches;
    408 }
    409 
    410 /**
    411  * @interface
    412  */
    413 WebInspector.SearchScope = function()
    414 {
    415 }
    416 
    417 WebInspector.SearchScope.prototype = {
    418     /**
    419      * @param {!WebInspector.SearchConfig} searchConfig
    420      * @param {!WebInspector.Progress} progress
    421      * @param {function(!WebInspector.FileBasedSearchResult)} searchResultCallback
    422      * @param {function(boolean)} searchFinishedCallback
    423      */
    424     performSearch: function(searchConfig, progress, searchResultCallback, searchFinishedCallback) { },
    425 
    426     /**
    427      * @param {!WebInspector.Progress} progress
    428      * @param {function(boolean)} callback
    429      */
    430     performIndexing: function(progress, callback) { },
    431 
    432     stopSearch: function() { },
    433 
    434     /**
    435      * @param {!WebInspector.ProjectSearchConfig} searchConfig
    436      * @return {!WebInspector.SearchResultsPane}
    437      */
    438     createSearchResultsPane: function(searchConfig) { }
    439 }
    440 
    441 importScript("FileBasedSearchResultsPane.js");
    442 importScript("SourcesSearchScope.js");
    443