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