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.SearchResultsPane} 8 * @param {!WebInspector.ProjectSearchConfig} searchConfig 9 */ 10 WebInspector.FileBasedSearchResultsPane = function(searchConfig) 11 { 12 WebInspector.SearchResultsPane.call(this, searchConfig); 13 14 this._searchResults = []; 15 16 this.element.id = "search-results-pane-file-based"; 17 18 this._treeOutlineElement = document.createElement("ol"); 19 this._treeOutlineElement.className = "search-results-outline-disclosure"; 20 this.element.appendChild(this._treeOutlineElement); 21 this._treeOutline = new TreeOutline(this._treeOutlineElement); 22 23 this._matchesExpandedCount = 0; 24 } 25 26 WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount = 20; 27 WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce = 20; 28 29 WebInspector.FileBasedSearchResultsPane.prototype = { 30 /** 31 * @param {!WebInspector.FileBasedSearchResult} searchResult 32 */ 33 addSearchResult: function(searchResult) 34 { 35 this._searchResults.push(searchResult); 36 var uiSourceCode = searchResult.uiSourceCode; 37 if (!uiSourceCode) 38 return; 39 this._addFileTreeElement(searchResult); 40 }, 41 42 /** 43 * @param {!WebInspector.FileBasedSearchResult} searchResult 44 */ 45 _addFileTreeElement: function(searchResult) 46 { 47 var fileTreeElement = new WebInspector.FileBasedSearchResultsPane.FileTreeElement(this._searchConfig, searchResult); 48 this._treeOutline.appendChild(fileTreeElement); 49 // Expand until at least a certain number of matches is expanded. 50 if (this._matchesExpandedCount < WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount) 51 fileTreeElement.expand(); 52 this._matchesExpandedCount += searchResult.searchMatches.length; 53 }, 54 55 __proto__: WebInspector.SearchResultsPane.prototype 56 } 57 58 /** 59 * @constructor 60 * @extends {TreeElement} 61 * @param {!WebInspector.ProjectSearchConfig} searchConfig 62 * @param {!WebInspector.FileBasedSearchResult} searchResult 63 */ 64 WebInspector.FileBasedSearchResultsPane.FileTreeElement = function(searchConfig, searchResult) 65 { 66 TreeElement.call(this, "", null, true); 67 this._searchConfig = searchConfig; 68 this._searchResult = searchResult; 69 70 this.toggleOnClick = true; 71 this.selectable = false; 72 } 73 74 WebInspector.FileBasedSearchResultsPane.FileTreeElement.prototype = { 75 onexpand: function() 76 { 77 if (this._initialized) 78 return; 79 80 this._updateMatchesUI(); 81 this._initialized = true; 82 }, 83 84 _updateMatchesUI: function() 85 { 86 this.removeChildren(); 87 var toIndex = Math.min(this._searchResult.searchMatches.length, WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce); 88 if (toIndex < this._searchResult.searchMatches.length) { 89 this._appendSearchMatches(0, toIndex - 1); 90 this._appendShowMoreMatchesElement(toIndex - 1); 91 } else { 92 this._appendSearchMatches(0, toIndex); 93 } 94 }, 95 96 onattach: function() 97 { 98 this._updateSearchMatches(); 99 }, 100 101 _updateSearchMatches: function() 102 { 103 this.listItemElement.classList.add("search-result"); 104 105 var fileNameSpan = document.createElement("span"); 106 fileNameSpan.className = "search-result-file-name"; 107 fileNameSpan.textContent = this._searchResult.uiSourceCode.fullDisplayName(); 108 this.listItemElement.appendChild(fileNameSpan); 109 110 var matchesCountSpan = document.createElement("span"); 111 matchesCountSpan.className = "search-result-matches-count"; 112 113 var searchMatchesCount = this._searchResult.searchMatches.length; 114 if (searchMatchesCount === 1) 115 matchesCountSpan.textContent = WebInspector.UIString("(%d match)", searchMatchesCount); 116 else 117 matchesCountSpan.textContent = WebInspector.UIString("(%d matches)", searchMatchesCount); 118 119 this.listItemElement.appendChild(matchesCountSpan); 120 if (this.expanded) 121 this._updateMatchesUI(); 122 }, 123 124 /** 125 * @param {number} fromIndex 126 * @param {number} toIndex 127 */ 128 _appendSearchMatches: function(fromIndex, toIndex) 129 { 130 var searchResult = this._searchResult; 131 var uiSourceCode = searchResult.uiSourceCode; 132 var searchMatches = searchResult.searchMatches; 133 134 var queries = this._searchConfig.queries(); 135 var regexes = []; 136 for (var i = 0; i < queries.length; ++i) 137 regexes.push(createSearchRegex(queries[i], !this._searchConfig.ignoreCase(), this._searchConfig.isRegex())); 138 139 for (var i = fromIndex; i < toIndex; ++i) { 140 var lineNumber = searchMatches[i].lineNumber; 141 var lineContent = searchMatches[i].lineContent; 142 var matchRanges = []; 143 for (var j = 0; j < regexes.length; ++j) 144 matchRanges = matchRanges.concat(this._regexMatchRanges(lineContent, regexes[j])); 145 146 var anchor = this._createAnchor(uiSourceCode, lineNumber, matchRanges[0].offset); 147 148 var numberString = numberToStringWithSpacesPadding(lineNumber + 1, 4); 149 var lineNumberSpan = document.createElement("span"); 150 lineNumberSpan.classList.add("search-match-line-number"); 151 lineNumberSpan.textContent = numberString; 152 anchor.appendChild(lineNumberSpan); 153 154 var contentSpan = this._createContentSpan(lineContent, matchRanges); 155 anchor.appendChild(contentSpan); 156 157 var searchMatchElement = new TreeElement(""); 158 searchMatchElement.selectable = false; 159 this.appendChild(searchMatchElement); 160 searchMatchElement.listItemElement.className = "search-match source-code"; 161 searchMatchElement.listItemElement.appendChild(anchor); 162 } 163 }, 164 165 /** 166 * @param {number} startMatchIndex 167 */ 168 _appendShowMoreMatchesElement: function(startMatchIndex) 169 { 170 var matchesLeftCount = this._searchResult.searchMatches.length - startMatchIndex; 171 var showMoreMatchesText = WebInspector.UIString("Show all matches (%d more).", matchesLeftCount); 172 this._showMoreMatchesTreeElement = new TreeElement(showMoreMatchesText); 173 this.appendChild(this._showMoreMatchesTreeElement); 174 this._showMoreMatchesTreeElement.listItemElement.classList.add("show-more-matches"); 175 this._showMoreMatchesTreeElement.onselect = this._showMoreMatchesElementSelected.bind(this, startMatchIndex); 176 }, 177 178 /** 179 * @param {!WebInspector.UISourceCode} uiSourceCode 180 * @param {number} lineNumber 181 * @param {number} columnNumber 182 * @return {!Element} 183 */ 184 _createAnchor: function(uiSourceCode, lineNumber, columnNumber) 185 { 186 return WebInspector.Linkifier.linkifyUsingRevealer(uiSourceCode.uiLocation(lineNumber, columnNumber), "", uiSourceCode.url, lineNumber); 187 }, 188 189 /** 190 * @param {string} lineContent 191 * @param {!Array.<!WebInspector.SourceRange>} matchRanges 192 */ 193 _createContentSpan: function(lineContent, matchRanges) 194 { 195 var contentSpan = document.createElement("span"); 196 contentSpan.className = "search-match-content"; 197 contentSpan.textContent = lineContent; 198 WebInspector.highlightRangesWithStyleClass(contentSpan, matchRanges, "highlighted-match"); 199 return contentSpan; 200 }, 201 202 /** 203 * @param {string} lineContent 204 * @param {!RegExp} regex 205 * @return {!Array.<!WebInspector.SourceRange>} 206 */ 207 _regexMatchRanges: function(lineContent, regex) 208 { 209 regex.lastIndex = 0; 210 var match; 211 var offset = 0; 212 var matchRanges = []; 213 while ((regex.lastIndex < lineContent.length) && (match = regex.exec(lineContent))) 214 matchRanges.push(new WebInspector.SourceRange(match.index, match[0].length)); 215 216 return matchRanges; 217 }, 218 219 /** 220 * @param {number} startMatchIndex 221 * @return {boolean} 222 */ 223 _showMoreMatchesElementSelected: function(startMatchIndex) 224 { 225 this.removeChild(this._showMoreMatchesTreeElement); 226 this._appendSearchMatches(startMatchIndex, this._searchResult.searchMatches.length); 227 return false; 228 }, 229 230 __proto__: TreeElement.prototype 231 } 232