Home | History | Annotate | Download | only in front_end
      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  */
     32 WebInspector.AdvancedSearchController = function()
     33 {
     34     this._shortcut = WebInspector.AdvancedSearchController.createShortcut();
     35     this._searchId = 0;
     36 
     37     WebInspector.settings.advancedSearchConfig = WebInspector.settings.createSetting("advancedSearchConfig", new WebInspector.SearchConfig("", true, false));
     38 
     39     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
     40 }
     41 
     42 /**
     43  * @return {!WebInspector.KeyboardShortcut.Descriptor}
     44  */
     45 WebInspector.AdvancedSearchController.createShortcut = function()
     46 {
     47     if (WebInspector.isMac())
     48         return WebInspector.KeyboardShortcut.makeDescriptor("f", WebInspector.KeyboardShortcut.Modifiers.Meta | WebInspector.KeyboardShortcut.Modifiers.Alt);
     49     else
     50         return WebInspector.KeyboardShortcut.makeDescriptor("f", WebInspector.KeyboardShortcut.Modifiers.Ctrl | WebInspector.KeyboardShortcut.Modifiers.Shift);
     51 }
     52 
     53 WebInspector.AdvancedSearchController.prototype = {
     54     /**
     55      * @param {KeyboardEvent} event
     56      * @return {boolean}
     57      */
     58     handleShortcut: function(event)
     59     {
     60         if (WebInspector.KeyboardShortcut.makeKeyFromEvent(event) === this._shortcut.key) {
     61             if (!this._searchView || !this._searchView.isShowing() || this._searchView._search !== document.activeElement) {
     62                 WebInspector.showPanel("scripts");
     63                 this.show();
     64             } else
     65                 this.close();
     66             event.consume(true);
     67             return true;
     68         }
     69         return false;
     70     },
     71 
     72     _frameNavigated: function()
     73     {
     74         this.resetSearch();
     75     },
     76 
     77     /**
     78      * @param {WebInspector.SearchScope} searchScope
     79      */
     80     registerSearchScope: function(searchScope)
     81     {
     82         // FIXME: implement multiple search scopes.
     83         this._searchScope = searchScope;
     84     },
     85 
     86     show: function()
     87     {
     88         if (!this._searchView)
     89             this._searchView = new WebInspector.SearchView(this);
     90 
     91         this._searchView.syncToSelection();
     92 
     93         if (this._searchView.isShowing())
     94             this._searchView.focus();
     95         else
     96             WebInspector.showViewInDrawer(this._searchView._searchPanelElement, this._searchView, this.stopSearch.bind(this));
     97         this.startIndexing();
     98     },
     99 
    100     close: function()
    101     {
    102         this.stopSearch();
    103         WebInspector.closeViewInDrawer();
    104     },
    105 
    106     /**
    107      * @param {boolean} finished
    108      */
    109     _onIndexingFinished: function(finished)
    110     {
    111         delete this._isIndexing;
    112         this._searchView.indexingFinished(finished);
    113         if (!finished)
    114             delete this._pendingSearchConfig;
    115         if (!this._pendingSearchConfig)
    116             return;
    117         var searchConfig = this._pendingSearchConfig
    118         delete this._pendingSearchConfig;
    119         this._innerStartSearch(searchConfig);
    120     },
    121 
    122     startIndexing: function()
    123     {
    124         this._isIndexing = true;
    125         // FIXME: this._currentSearchScope should be initialized based on searchConfig
    126         this._currentSearchScope = this._searchScope;
    127         if (this._progressIndicator)
    128             this._progressIndicator.done();
    129         this._progressIndicator = new WebInspector.ProgressIndicator();
    130         this._searchView.indexingStarted(this._progressIndicator);
    131         this._currentSearchScope.performIndexing(this._progressIndicator, this._onIndexingFinished.bind(this));
    132     },
    133 
    134     /**
    135      * @param {number} searchId
    136      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    137      */
    138     _onSearchResult: function(searchId, searchResult)
    139     {
    140         if (searchId !== this._searchId)
    141             return;
    142         this._searchView.addSearchResult(searchResult);
    143         if (!searchResult.searchMatches.length)
    144             return;
    145         if (!this._searchResultsPane)
    146             this._searchResultsPane = this._currentSearchScope.createSearchResultsPane(this._searchConfig);
    147         this._searchView.resultsPane = this._searchResultsPane;
    148         this._searchResultsPane.addSearchResult(searchResult);
    149     },
    150 
    151     /**
    152      * @param {number} searchId
    153      * @param {boolean} finished
    154      */
    155     _onSearchFinished: function(searchId, finished)
    156     {
    157         if (searchId !== this._searchId)
    158             return;
    159         if (!this._searchResultsPane)
    160             this._searchView.nothingFound();
    161         this._searchView.searchFinished(finished);
    162         delete this._searchConfig;
    163     },
    164 
    165     /**
    166      * @param {WebInspector.SearchConfig} searchConfig
    167      */
    168     startSearch: function(searchConfig)
    169     {
    170         this.resetSearch();
    171         ++this._searchId;
    172         if (!this._isIndexing)
    173             this.startIndexing();
    174         this._pendingSearchConfig = searchConfig;
    175     },
    176 
    177     /**
    178      * @param {WebInspector.SearchConfig} searchConfig
    179      */
    180     _innerStartSearch: function(searchConfig)
    181     {
    182         this._searchConfig = searchConfig;
    183         // FIXME: this._currentSearchScope should be initialized based on searchConfig
    184         this._currentSearchScope = this._searchScope;
    185 
    186         if (this._progressIndicator)
    187             this._progressIndicator.done();
    188         this._progressIndicator = new WebInspector.ProgressIndicator();
    189         var totalSearchResultsCount = this._currentSearchScope.performSearch(searchConfig, this._progressIndicator, this._onSearchResult.bind(this, this._searchId), this._onSearchFinished.bind(this, this._searchId));
    190         this._searchView.searchStarted(this._progressIndicator);
    191     },
    192 
    193     resetSearch: function()
    194     {
    195         this.stopSearch();
    196 
    197         if (this._searchResultsPane) {
    198             this._searchView.resetResults();
    199             delete this._searchResultsPane;
    200         }
    201     },
    202 
    203     stopSearch: function()
    204     {
    205         if (this._progressIndicator)
    206             this._progressIndicator.cancel();
    207         if (this._currentSearchScope)
    208             this._currentSearchScope.stopSearch();
    209         delete this._searchConfig;
    210     }
    211 }
    212 
    213 /**
    214  * @constructor
    215  * @extends {WebInspector.View}
    216  * @param {WebInspector.AdvancedSearchController} controller
    217  */
    218 WebInspector.SearchView = function(controller)
    219 {
    220     WebInspector.View.call(this);
    221 
    222     this._controller = controller;
    223 
    224     this.element.className = "search-view";
    225 
    226     this._searchPanelElement = document.createElement("span");
    227     this._searchPanelElement.className = "search-drawer-header";
    228     this._searchPanelElement.addEventListener("keydown", this._onKeyDown.bind(this), false);
    229 
    230     this._searchResultsElement = this.element.createChild("div");
    231     this._searchResultsElement.className = "search-results";
    232 
    233     this._searchLabel = this._searchPanelElement.createChild("span");
    234     this._searchLabel.textContent = WebInspector.UIString("Search sources");
    235     this._search = this._searchPanelElement.createChild("input");
    236     this._search.setAttribute("type", "search");
    237     this._search.addStyleClass("search-config-search");
    238     this._search.setAttribute("results", "0");
    239     this._search.setAttribute("size", 30);
    240 
    241     this._ignoreCaseLabel = this._searchPanelElement.createChild("label");
    242     this._ignoreCaseLabel.addStyleClass("search-config-label");
    243     this._ignoreCaseCheckbox = this._ignoreCaseLabel.createChild("input");
    244     this._ignoreCaseCheckbox.setAttribute("type", "checkbox");
    245     this._ignoreCaseCheckbox.addStyleClass("search-config-checkbox");
    246     this._ignoreCaseLabel.appendChild(document.createTextNode(WebInspector.UIString("Ignore case")));
    247 
    248     this._regexLabel = this._searchPanelElement.createChild("label");
    249     this._regexLabel.addStyleClass("search-config-label");
    250     this._regexCheckbox = this._regexLabel.createChild("input");
    251     this._regexCheckbox.setAttribute("type", "checkbox");
    252     this._regexCheckbox.addStyleClass("search-config-checkbox");
    253     this._regexLabel.appendChild(document.createTextNode(WebInspector.UIString("Regular expression")));
    254 
    255     this._searchStatusBarElement = document.createElement("div");
    256     this._searchStatusBarElement.className = "search-status-bar-item";
    257     this._searchMessageElement = this._searchStatusBarElement.createChild("div");
    258     this._searchMessageElement.className = "search-status-bar-message";
    259 
    260     this._searchResultsMessageElement = document.createElement("span");
    261     this._searchResultsMessageElement.className = "search-results-status-bar-message";
    262 
    263     this._load();
    264 }
    265 
    266 // Number of recent search queries to store.
    267 WebInspector.SearchView.maxQueriesCount = 20;
    268 
    269 WebInspector.SearchView.prototype = {
    270     /**
    271      * @return {Array.<Element>}
    272      */
    273     get statusBarItems()
    274     {
    275         return [this._searchStatusBarElement, this._searchResultsMessageElement];
    276     },
    277 
    278     /**
    279      * @return {WebInspector.SearchConfig}
    280      */
    281     get searchConfig()
    282     {
    283         return new WebInspector.SearchConfig(this._search.value, this._ignoreCaseCheckbox.checked, this._regexCheckbox.checked);
    284     },
    285 
    286     syncToSelection: function()
    287     {
    288         var selection = window.getSelection();
    289         if (selection.rangeCount) {
    290             var queryCandidate = selection.toString().replace(/\r?\n.*/, "");
    291             if (queryCandidate)
    292                 this._search.value = queryCandidate;
    293         }
    294     },
    295 
    296     /**
    297      * @type {WebInspector.SearchResultsPane}
    298      */
    299     set resultsPane(resultsPane)
    300     {
    301         this.resetResults();
    302         this._searchResultsElement.appendChild(resultsPane.element);
    303     },
    304 
    305     /**
    306      * @param {WebInspector.ProgressIndicator} progressIndicator
    307      */
    308     searchStarted: function(progressIndicator)
    309     {
    310         this.resetResults();
    311         this._resetCounters();
    312 
    313         this._searchMessageElement.textContent = WebInspector.UIString("Searching...");
    314         progressIndicator.show(this._searchStatusBarElement);
    315         this._updateSearchResultsMessage();
    316 
    317         if (!this._searchingView)
    318             this._searchingView = new WebInspector.EmptyView(WebInspector.UIString("Searching..."));
    319         this._searchingView.show(this._searchResultsElement);
    320     },
    321 
    322     /**
    323      * @param {WebInspector.ProgressIndicator} progressIndicator
    324      */
    325     indexingStarted: function(progressIndicator)
    326     {
    327         this._searchMessageElement.textContent = WebInspector.UIString("Indexing...");
    328         progressIndicator.show(this._searchStatusBarElement);
    329     },
    330 
    331     /**
    332      * @param {boolean} finished
    333      */
    334     indexingFinished: function(finished)
    335     {
    336         this._searchMessageElement.textContent = finished ? "" : WebInspector.UIString("Indexing interrupted.");
    337     },
    338 
    339     _updateSearchResultsMessage: function()
    340     {
    341         if (this._searchMatchesCount && this._searchResultsCount)
    342             this._searchResultsMessageElement.textContent = WebInspector.UIString("Found %d matches in %d files.", this._searchMatchesCount, this._nonEmptySearchResultsCount);
    343         else
    344             this._searchResultsMessageElement.textContent = "";
    345     },
    346 
    347     resetResults: function()
    348     {
    349         if (this._searchingView)
    350             this._searchingView.detach();
    351         if (this._notFoundView)
    352             this._notFoundView.detach();
    353         this._searchResultsElement.removeChildren();
    354     },
    355 
    356     _resetCounters: function()
    357     {
    358         this._searchMatchesCount = 0;
    359         this._searchResultsCount = 0;
    360         this._nonEmptySearchResultsCount = 0;
    361     },
    362 
    363     nothingFound: function()
    364     {
    365         this.resetResults();
    366 
    367         if (!this._notFoundView)
    368             this._notFoundView = new WebInspector.EmptyView(WebInspector.UIString("No matches found."));
    369         this._notFoundView.show(this._searchResultsElement);
    370         this._searchResultsMessageElement.textContent = WebInspector.UIString("No matches found.");
    371     },
    372 
    373     /**
    374      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    375      */
    376     addSearchResult: function(searchResult)
    377     {
    378         this._searchMatchesCount += searchResult.searchMatches.length;
    379         this._searchResultsCount++;
    380         if (searchResult.searchMatches.length)
    381             this._nonEmptySearchResultsCount++;
    382         this._updateSearchResultsMessage();
    383     },
    384 
    385     /**
    386      * @param {boolean} finished
    387      */
    388     searchFinished: function(finished)
    389     {
    390         this._searchMessageElement.textContent = finished ? WebInspector.UIString("Search finished.") : WebInspector.UIString("Search interrupted.");
    391     },
    392 
    393     focus: function()
    394     {
    395         WebInspector.setCurrentFocusElement(this._search);
    396         this._search.select();
    397     },
    398 
    399     wasShown: function()
    400     {
    401         this.focus();
    402     },
    403 
    404     willHide: function()
    405     {
    406         this._controller.stopSearch();
    407     },
    408 
    409     /**
    410      * @param {Event} event
    411      */
    412     _onKeyDown: function(event)
    413     {
    414         switch (event.keyCode) {
    415         case WebInspector.KeyboardShortcut.Keys.Enter.code:
    416             this._onAction();
    417             break;
    418         case WebInspector.KeyboardShortcut.Keys.Esc.code:
    419             this._controller.close();
    420             event.consume(true);
    421             break;
    422         }
    423     },
    424 
    425     _save: function()
    426     {
    427         var searchConfig = new WebInspector.SearchConfig(this.searchConfig.query, this.searchConfig.ignoreCase, this.searchConfig.isRegex);
    428         WebInspector.settings.advancedSearchConfig.set(searchConfig);
    429     },
    430 
    431     _load: function()
    432     {
    433         var searchConfig = WebInspector.settings.advancedSearchConfig.get();
    434         this._search.value = searchConfig.query;
    435         this._ignoreCaseCheckbox.checked = searchConfig.ignoreCase;
    436         this._regexCheckbox.checked = searchConfig.isRegex;
    437     },
    438 
    439     _onAction: function()
    440     {
    441         if (!this.searchConfig.query || !this.searchConfig.query.length)
    442             return;
    443 
    444         this._save();
    445         this._controller.startSearch(this.searchConfig);
    446     },
    447 
    448     __proto__: WebInspector.View.prototype
    449 }
    450 
    451 
    452 /**
    453  * @constructor
    454  * @param {string} query
    455  * @param {boolean} ignoreCase
    456  * @param {boolean} isRegex
    457  */
    458 WebInspector.SearchConfig = function(query, ignoreCase, isRegex)
    459 {
    460     this.query = query;
    461     this.ignoreCase = ignoreCase;
    462     this.isRegex = isRegex;
    463 }
    464 
    465 /**
    466  * @interface
    467  */
    468 WebInspector.SearchScope = function()
    469 {
    470 }
    471 
    472 WebInspector.SearchScope.prototype = {
    473     /**
    474      * @param {WebInspector.SearchConfig} searchConfig
    475      * @param {WebInspector.Progress} progress
    476      * @param {function(WebInspector.FileBasedSearchResultsPane.SearchResult)} searchResultCallback
    477      * @param {function(boolean)} searchFinishedCallback
    478      */
    479     performSearch: function(searchConfig, progress, searchResultCallback, searchFinishedCallback) { },
    480 
    481     stopSearch: function() { },
    482 
    483     /**
    484      * @param {WebInspector.SearchConfig} searchConfig
    485      * @return {WebInspector.SearchResultsPane}
    486      */
    487     createSearchResultsPane: function(searchConfig) { }
    488 }
    489 
    490 /**
    491  * @constructor
    492  * @param {number} offset
    493  * @param {number} length
    494  */
    495 WebInspector.SearchResult = function(offset, length)
    496 {
    497     this.offset = offset;
    498     this.length = length;
    499 }
    500 
    501 /**
    502  * @constructor
    503  * @param {WebInspector.SearchConfig} searchConfig
    504  */
    505 WebInspector.SearchResultsPane = function(searchConfig)
    506 {
    507     this._searchConfig = searchConfig;
    508     this.element = document.createElement("div");
    509 }
    510 
    511 WebInspector.SearchResultsPane.prototype = {
    512     /**
    513      * @return {WebInspector.SearchConfig}
    514      */
    515     get searchConfig()
    516     {
    517         return this._searchConfig;
    518     },
    519 
    520     /**
    521      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    522      */
    523     addSearchResult: function(searchResult) { }
    524 }
    525 
    526 /**
    527  * @constructor
    528  * @extends {WebInspector.SearchResultsPane}
    529  * @param {WebInspector.SearchConfig} searchConfig
    530  */
    531 WebInspector.FileBasedSearchResultsPane = function(searchConfig)
    532 {
    533     WebInspector.SearchResultsPane.call(this, searchConfig);
    534 
    535     this._searchResults = [];
    536 
    537     this.element.id ="search-results-pane-file-based";
    538 
    539     this._treeOutlineElement = document.createElement("ol");
    540     this._treeOutlineElement.className = "search-results-outline-disclosure";
    541     this.element.appendChild(this._treeOutlineElement);
    542     this._treeOutline = new TreeOutline(this._treeOutlineElement);
    543 
    544     this._matchesExpandedCount = 0;
    545 }
    546 
    547 WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount = 20;
    548 WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce = 20;
    549 
    550 WebInspector.FileBasedSearchResultsPane.prototype = {
    551     /**
    552      * @param {WebInspector.UISourceCode} uiSourceCode
    553      * @param {number} lineNumber
    554      * @param {number} columnNumber
    555      * @return {Element}
    556      */
    557     _createAnchor: function(uiSourceCode, lineNumber, columnNumber)
    558     {
    559         var anchor = document.createElement("a");
    560         anchor.preferredPanel = "scripts";
    561         anchor.href = sanitizeHref(uiSourceCode.originURL());
    562         anchor.uiSourceCode = uiSourceCode;
    563         anchor.lineNumber = lineNumber;
    564         return anchor;
    565     },
    566 
    567     /**
    568      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    569      */
    570     addSearchResult: function(searchResult)
    571     {
    572         this._searchResults.push(searchResult);
    573         var uiSourceCode = searchResult.uiSourceCode;
    574         if (!uiSourceCode)
    575             return;
    576         var searchMatches = searchResult.searchMatches;
    577 
    578         var fileTreeElement = this._addFileTreeElement(uiSourceCode.fullDisplayName(), searchMatches.length, this._searchResults.length - 1);
    579     },
    580 
    581     /**
    582      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    583      * @param {TreeElement} fileTreeElement
    584      */
    585     _fileTreeElementExpanded: function(searchResult, fileTreeElement)
    586     {
    587         if (fileTreeElement._initialized)
    588             return;
    589 
    590         var toIndex = Math.min(searchResult.searchMatches.length, WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce);
    591         if (toIndex < searchResult.searchMatches.length) {
    592             this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex - 1);
    593             this._appendShowMoreMatchesElement(fileTreeElement, searchResult, toIndex - 1);
    594         } else
    595             this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex);
    596 
    597         fileTreeElement._initialized = true;
    598     },
    599 
    600     /**
    601      * @param {TreeElement} fileTreeElement
    602      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    603      * @param {number} fromIndex
    604      * @param {number} toIndex
    605      */
    606     _appendSearchMatches: function(fileTreeElement, searchResult, fromIndex, toIndex)
    607     {
    608         var uiSourceCode = searchResult.uiSourceCode;
    609         var searchMatches = searchResult.searchMatches;
    610 
    611         var regex = createSearchRegex(this._searchConfig.query, !this._searchConfig.ignoreCase, this._searchConfig.isRegex);
    612         for (var i = fromIndex; i < toIndex; ++i) {
    613             var lineNumber = searchMatches[i].lineNumber;
    614             var lineContent = searchMatches[i].lineContent;
    615             var matchRanges = this._regexMatchRanges(lineContent, regex);
    616 
    617             var anchor = this._createAnchor(uiSourceCode, lineNumber, matchRanges[0].offset);
    618 
    619             var numberString = numberToStringWithSpacesPadding(lineNumber + 1, 4);
    620             var lineNumberSpan = document.createElement("span");
    621             lineNumberSpan.addStyleClass("search-match-line-number");
    622             lineNumberSpan.textContent = numberString;
    623             anchor.appendChild(lineNumberSpan);
    624 
    625             var contentSpan = this._createContentSpan(lineContent, matchRanges);
    626             anchor.appendChild(contentSpan);
    627 
    628             var searchMatchElement = new TreeElement("", null, false);
    629             searchMatchElement.selectable = false;
    630             fileTreeElement.appendChild(searchMatchElement);
    631             searchMatchElement.listItemElement.className = "search-match source-code";
    632             searchMatchElement.listItemElement.appendChild(anchor);
    633         }
    634     },
    635 
    636     /**
    637      * @param {TreeElement} fileTreeElement
    638      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    639      * @param {number} startMatchIndex
    640      */
    641     _appendShowMoreMatchesElement: function(fileTreeElement, searchResult, startMatchIndex)
    642     {
    643         var matchesLeftCount = searchResult.searchMatches.length - startMatchIndex;
    644         var showMoreMatchesText = WebInspector.UIString("Show all matches (%d more).", matchesLeftCount);
    645         var showMoreMatchesElement = new TreeElement(showMoreMatchesText, null, false);
    646         fileTreeElement.appendChild(showMoreMatchesElement);
    647         showMoreMatchesElement.listItemElement.addStyleClass("show-more-matches");
    648         showMoreMatchesElement.onselect = this._showMoreMatchesElementSelected.bind(this, searchResult, startMatchIndex, showMoreMatchesElement);
    649     },
    650 
    651     /**
    652      * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
    653      * @param {number} startMatchIndex
    654      * @param {TreeElement} showMoreMatchesElement
    655      */
    656     _showMoreMatchesElementSelected: function(searchResult, startMatchIndex, showMoreMatchesElement)
    657     {
    658         var fileTreeElement = showMoreMatchesElement.parent;
    659         fileTreeElement.removeChild(showMoreMatchesElement);
    660         this._appendSearchMatches(fileTreeElement, searchResult, startMatchIndex, searchResult.searchMatches.length);
    661     },
    662 
    663     /**
    664      * @param {string} fileName
    665      * @param {number} searchMatchesCount
    666      * @param {number} searchResultIndex
    667      */
    668     _addFileTreeElement: function(fileName, searchMatchesCount, searchResultIndex)
    669     {
    670         var fileTreeElement = new TreeElement("", null, true);
    671         fileTreeElement.toggleOnClick = true;
    672         fileTreeElement.selectable = false;
    673 
    674         this._treeOutline.appendChild(fileTreeElement);
    675         fileTreeElement.listItemElement.addStyleClass("search-result");
    676 
    677         var fileNameSpan = document.createElement("span");
    678         fileNameSpan.className = "search-result-file-name";
    679         fileNameSpan.textContent = fileName;
    680         fileTreeElement.listItemElement.appendChild(fileNameSpan);
    681 
    682         var matchesCountSpan = document.createElement("span");
    683         matchesCountSpan.className = "search-result-matches-count";
    684         if (searchMatchesCount === 1)
    685             matchesCountSpan.textContent = WebInspector.UIString("(%d match)", searchMatchesCount);
    686         else
    687             matchesCountSpan.textContent = WebInspector.UIString("(%d matches)", searchMatchesCount);
    688 
    689         fileTreeElement.listItemElement.appendChild(matchesCountSpan);
    690 
    691         var searchResult = this._searchResults[searchResultIndex];
    692         fileTreeElement.onexpand = this._fileTreeElementExpanded.bind(this, searchResult, fileTreeElement);
    693 
    694         // Expand until at least certain amount of matches is expanded.
    695         if (this._matchesExpandedCount < WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount)
    696             fileTreeElement.expand();
    697         this._matchesExpandedCount += searchResult.searchMatches.length;
    698 
    699         return fileTreeElement;
    700     },
    701 
    702     /**
    703      * @param {string} lineContent
    704      * @param {RegExp} regex
    705      * @return {Array.<WebInspector.SearchResult>}
    706      */
    707     _regexMatchRanges: function(lineContent, regex)
    708     {
    709         regex.lastIndex = 0;
    710         var match;
    711         var offset = 0;
    712         var matchRanges = [];
    713         while ((regex.lastIndex < lineContent.length) && (match = regex.exec(lineContent)))
    714             matchRanges.push(new WebInspector.SearchResult(match.index, match[0].length));
    715 
    716         return matchRanges;
    717     },
    718 
    719     /**
    720      * @param {string} lineContent
    721      * @param {Array.<WebInspector.SearchResult>} matchRanges
    722      */
    723     _createContentSpan: function(lineContent, matchRanges)
    724     {
    725         var contentSpan = document.createElement("span");
    726         contentSpan.className = "search-match-content";
    727         contentSpan.textContent = lineContent;
    728         WebInspector.highlightRangesWithStyleClass(contentSpan, matchRanges, "highlighted-match");
    729         return contentSpan;
    730     },
    731 
    732     __proto__: WebInspector.SearchResultsPane.prototype
    733 }
    734 
    735 /**
    736  * @constructor
    737  * @param {WebInspector.UISourceCode} uiSourceCode
    738  * @param {Array.<Object>} searchMatches
    739  */
    740 WebInspector.FileBasedSearchResultsPane.SearchResult = function(uiSourceCode, searchMatches) {
    741     this.uiSourceCode = uiSourceCode;
    742     this.searchMatches = searchMatches;
    743 }
    744 
    745 /**
    746  * @type {WebInspector.AdvancedSearchController}
    747  */
    748 WebInspector.advancedSearchController = null;
    749