Index: Source/WebCore/inspector/front-end/AdvancedSearchController.js |
=================================================================== |
--- Source/WebCore/inspector/front-end/AdvancedSearchController.js (revision 98335) |
+++ Source/WebCore/inspector/front-end/AdvancedSearchController.js (working copy) |
@@ -83,6 +83,10 @@ |
if (searchId !== this._searchId) |
return; |
+ this._searchView.addSearchResult(searchResult); |
+ if (!searchResult.searchMatches.length) |
+ return; |
+ |
if (!this._searchResultsPane) |
this._searchResultsPane = this._currentSearchScope.createSearchResultsPane(this._searchConfig); |
this._searchView.resultsPane = this._searchResultsPane; |
@@ -91,8 +95,9 @@ |
/** |
* @param {number} searchId |
+ * @param {boolean} finished |
*/ |
- _onSearchFinished: function(searchId) |
+ _onSearchFinished: function(searchId, finished) |
{ |
if (searchId !== this._searchId) |
return; |
@@ -100,7 +105,7 @@ |
if (!this._searchResultsPane) |
this._searchView.nothingFound(); |
- this._searchView.searchFinished(); |
+ this._searchView.searchFinished(finished); |
}, |
/** |
@@ -108,20 +113,29 @@ |
*/ |
startSearch: function(searchConfig) |
{ |
- this.stopSearch(); |
+ this.resetSearch(); |
+ ++this._searchId; |
this._searchConfig = searchConfig; |
// FIXME: this._currentSearchScope should be initialized based on searchConfig |
this._currentSearchScope = this._searchScope; |
- this._searchView.searchStarted(); |
- this._currentSearchScope.performSearch(searchConfig, this._onSearchResult.bind(this, this._searchId), this._onSearchFinished.bind(this, this._searchId)); |
+ var totalSearchResultsCount = this._currentSearchScope.performSearch(searchConfig, this._onSearchResult.bind(this, this._searchId), this._onSearchFinished.bind(this, this._searchId)); |
+ this._searchView.searchStarted(totalSearchResultsCount); |
}, |
+ resetSearch: function() |
+ { |
+ this.stopSearch(); |
+ |
+ if (this._searchResultsPane) { |
+ this._searchView.resetResults(); |
+ delete this._searchResultsPane; |
+ } |
+ }, |
+ |
stopSearch: function() |
{ |
- ++this._searchId; |
- delete this._searchResultsPane; |
if (this._currentSearchScope) |
this._currentSearchScope.stopSearch(); |
} |
@@ -162,7 +176,6 @@ |
this._ignoreCaseCheckbox.addStyleClass("search-config-checkbox"); |
this._ignoreCaseLabel.appendChild(document.createTextNode(WebInspector.UIString("Ignore case"))); |
- |
this._regexLabel = this._searchPanelElement.createChild("label"); |
this._regexLabel.addStyleClass("search-config-label"); |
this._regexCheckbox = this._regexLabel.createChild("input"); |
@@ -170,6 +183,22 @@ |
this._regexCheckbox.addStyleClass("search-config-checkbox"); |
this._regexLabel.appendChild(document.createTextNode(WebInspector.UIString("Regular expression"))); |
+ this._searchStatusBarElement = document.createElement("div"); |
+ this._searchStatusBarElement.className = "search-status-bar-item"; |
+ this._searchMessageElement = this._searchStatusBarElement.createChild("div"); |
+ this._searchMessageElement.className = "search-status-bar-message"; |
+ this._searchProgressElement = document.createElement("progress"); |
+ this._searchProgressElement.className = "search-status-bar-progress"; |
+ |
+ this._searchStopButtonItem = document.createElement("div"); |
+ this._searchStopButtonItem.className = "search-status-bar-stop-button-item"; |
+ this._searchStopStatusBarButton = new WebInspector.StatusBarButton(WebInspector.UIString("Stop search"), "search-status-bar-stop-button"); |
+ this._searchStopButtonItem.appendChild(this._searchStopStatusBarButton.element); |
+ this._searchStopStatusBarButton.addEventListener("click", this._searchStopButtonPressed, this); |
+ |
+ this._searchResultsMessageElement = document.createElement("span"); |
+ this._searchResultsMessageElement.className = "search-results-status-bar-message"; |
+ |
this._load(); |
} |
@@ -178,6 +207,22 @@ |
WebInspector.SearchView.prototype = { |
/** |
+ * @type {Array.<Element>} |
+ */ |
+ get statusBarItems() |
+ { |
+ return [this._searchStatusBarElement]; |
+ }, |
+ |
+ /** |
+ * @type {Element} |
+ */ |
+ get counterElement() |
+ { |
+ return this._searchResultsMessageElement; |
+ }, |
+ |
+ /** |
* @type {WebInspector.SearchConfig} |
*/ |
get searchConfig() |
@@ -194,31 +239,89 @@ |
*/ |
set resultsPane(resultsPane) |
{ |
- this._searchResultsElement.removeChildren(); |
+ this.resetResults(); |
this._searchResultsElement.appendChild(resultsPane.element); |
}, |
- searchStarted: function() |
+ /** |
+ * @param {number} totalSearchResultsCount |
+ */ |
+ searchStarted: function(totalSearchResultsCount) |
{ |
- // FIXME: This needs better UI. |
- var searchingView = new WebInspector.EmptyView(WebInspector.UIString("Searching...")) |
- this._searchResultsElement.removeChildren(); |
+ this.resetResults(); |
+ this._resetCounters(); |
+ |
+ this._totalSearchResultsCount = totalSearchResultsCount; |
+ |
+ this._searchMessageElement.textContent = WebInspector.UIString("Searching..."); |
+ this._searchStatusBarElement.appendChild(this._searchProgressElement); |
+ this._searchStatusBarElement.appendChild(this._searchStopButtonItem); |
+ this._updateSearchProgress(); |
+ |
+ this._updateSearchResultsMessage(); |
+ |
+ var searchingView = new WebInspector.EmptyView(WebInspector.UIString("Searching...")); |
searchingView.show(this._searchResultsElement); |
}, |
+ _updateSearchResultsMessage: function() |
+ { |
+ if (this._searchMatchesCount && this._searchResultsCount) |
+ this._searchResultsMessageElement.textContent = WebInspector.UIString("Found %d matches in %d files.", this._searchMatchesCount, this._nonEmptySearchResultsCount); |
+ else |
+ this._searchResultsMessageElement.textContent = ""; |
+ }, |
+ |
+ _updateSearchProgress: function() |
+ { |
+ this._searchProgressElement.setAttribute("max", this._totalSearchResultsCount); |
+ this._searchProgressElement.setAttribute("value", this._searchResultsCount); |
+ }, |
+ |
+ resetResults: function() |
+ { |
+ this._searchResultsElement.removeChildren(); |
+ }, |
+ |
+ _resetCounters: function() |
+ { |
+ this._searchMatchesCount = 0; |
+ this._searchResultsCount = 0; |
+ this._nonEmptySearchResultsCount = 0; |
+ }, |
+ |
nothingFound: function() |
{ |
- // FIXME: This needs better UI. |
- var notFoundView = new WebInspector.EmptyView(WebInspector.UIString("Nothing found")) |
- this._searchResultsElement.removeChildren(); |
+ this.resetResults(); |
+ |
+ var notFoundView = new WebInspector.EmptyView(WebInspector.UIString("No matches found.")); |
notFoundView.show(this._searchResultsElement); |
+ this._searchResultsMessageElement.textContent = WebInspector.UIString("No matches found."); |
}, |
- searchFinished: function() |
+ /** |
+ * @param {Object} searchResult |
+ */ |
+ addSearchResult: function(searchResult) |
{ |
- // FIXME: add message to drawer status bar |
+ this._searchMatchesCount += searchResult.searchMatches.length; |
+ this._searchResultsCount++; |
+ if (searchResult.searchMatches.length) |
+ this._nonEmptySearchResultsCount++; |
+ this._updateSearchResultsMessage(); |
+ this._updateSearchProgress(); |
}, |
+ /** |
+ * @param {boolean} finished |
+ */ |
+ searchFinished: function(finished) |
+ { |
+ this._searchMessageElement.textContent = finished ? WebInspector.UIString("Search finished.") : WebInspector.UIString("Search interrupted."); |
+ this._searchStatusBarElement.removeChild(this._searchProgressElement); |
+ this._searchStatusBarElement.removeChild(this._searchStopButtonItem); |
+ }, |
+ |
focus: function() |
{ |
WebInspector.currentFocusElement = this._search; |
@@ -258,6 +361,12 @@ |
this._regexCheckbox.checked = searchConfig.isRegex; |
}, |
+ _searchStopButtonPressed: function() |
+ { |
+ this._controller.stopSearch(); |
+ this.focus(); |
+ }, |
+ |
_onAction: function() |
{ |
this._save(); |
@@ -290,6 +399,8 @@ |
WebInspector.SearchScope.prototype = { |
/** |
* @param {WebInspector.SearchConfig} searchConfig |
+ * @param {function(Object)} searchResultCallback |
+ * @param {function(boolean)} searchFinishedCallback |
*/ |
performSearch: function(searchConfig, searchResultCallback, searchFinishedCallback) { }, |
@@ -342,10 +453,16 @@ |
this._treeOutlineElement = document.createElement("ol"); |
this._treeOutlineElement.className = "outline-disclosure"; |
+ this._treeOutlineElement.addStyleClass("search-results-outline-disclosure"); |
this.element.appendChild(this._treeOutlineElement); |
this._treeOutline = new TreeOutline(this._treeOutlineElement); |
+ |
+ this._matchesExpandedCount = 0; |
} |
+WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount = 20; |
+WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce = 20; |
+ |
WebInspector.FileBasedSearchResultsPane.prototype = { |
/** |
* @param {Object} file |
@@ -354,13 +471,13 @@ |
* @return {Element} |
*/ |
createAnchor: function(file, lineNumber, columnNumber) { }, |
- |
+ |
/** |
* @param {Object} file |
* @return {string} |
*/ |
fileName: function(file) { }, |
- |
+ |
/** |
* @param {Object} searchResult |
*/ |
@@ -370,12 +487,43 @@ |
var file = searchResult.file; |
var fileName = this.fileName(file); |
var searchMatches = searchResult.searchMatches; |
- |
- // Expand first file with matches only. |
- var fileTreeElement = this._addFileTreeElement(fileName, searchMatches.length, this._searchResults.length === 1); |
+ |
+ var fileTreeElement = this._addFileTreeElement(fileName, searchMatches.length, this._searchResults.length - 1); |
+ }, |
+ |
+ /** |
+ * @param {Object} searchResult |
+ * @param {TreeElement} fileTreeElement |
+ */ |
+ _fileTreeElementExpanded: function(searchResult, fileTreeElement) |
+ { |
+ if (fileTreeElement._initialized) |
+ return; |
+ var toIndex = Math.min(searchResult.searchMatches.length, WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce); |
+ if (toIndex < searchResult.searchMatches.length) { |
+ this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex - 1); |
+ this._appendShowMoreMatchesElement(fileTreeElement, searchResult, toIndex - 1); |
+ } else |
+ this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex); |
+ |
+ fileTreeElement._initialized = true; |
+ }, |
+ |
+ /** |
+ * @param {TreeElement} fileTreeElement |
+ * @param {Object} searchResult |
+ * @param {number} fromIndex |
+ * @param {number} toIndex |
+ */ |
+ _appendSearchMatches: function(fileTreeElement, searchResult, fromIndex, toIndex) |
+ { |
+ var file = searchResult.file; |
+ var fileName = this.fileName(file); |
+ var searchMatches = searchResult.searchMatches; |
+ |
var regex = createSearchRegex(this._searchConfig.query, !this._searchConfig.ignoreCase, this._searchConfig.isRegex); |
- for (var i = 0; i < searchMatches.length; i++) { |
+ for (var i = fromIndex; i < toIndex; ++i) { |
var lineNumber = searchMatches[i].lineNumber; |
var lineContent = searchMatches[i].lineContent; |
var matchRanges = this._regexMatchRanges(lineContent, regex); |
@@ -398,16 +546,42 @@ |
searchMatchElement.listItemElement.appendChild(anchor); |
} |
}, |
- |
+ |
/** |
+ * @param {TreeElement} fileTreeElement |
+ * @param {Object} searchResult |
+ * @param {number} startMatchIndex |
+ */ |
+ _appendShowMoreMatchesElement: function(fileTreeElement, searchResult, startMatchIndex) |
+ { |
+ var matchesLeftCount = searchResult.searchMatches.length - startMatchIndex; |
+ var showMoreMatchesText = WebInspector.UIString("Show all matches (%d more).", matchesLeftCount); |
+ var showMoreMatchesElement = new TreeElement(showMoreMatchesText, null, false); |
+ fileTreeElement.appendChild(showMoreMatchesElement); |
+ showMoreMatchesElement.listItemElement.addStyleClass("show-more-matches"); |
+ showMoreMatchesElement.onselect = this._showMoreMatchesElementSelected.bind(this, searchResult, startMatchIndex); |
+ }, |
+ |
+ /** |
+ * @param {Object} searchResult |
+ * @param {number} startMatchIndex |
+ * @param {TreeElement} showMoreMatchesElement |
+ */ |
+ _showMoreMatchesElementSelected: function(searchResult, startMatchIndex, showMoreMatchesElement) |
+ { |
+ var fileTreeElement = showMoreMatchesElement.parent; |
+ fileTreeElement.removeChild(showMoreMatchesElement); |
+ this._appendSearchMatches(fileTreeElement, searchResult, startMatchIndex, searchResult.searchMatches.length); |
+ }, |
+ |
+ /** |
* @param {string} fileName |
* @param {number} searchMatchesCount |
- * @param {boolean} expanded |
+ * @param {number} searchResultIndex |
*/ |
- _addFileTreeElement: function(fileName, searchMatchesCount, expanded) |
+ _addFileTreeElement: function(fileName, searchMatchesCount, searchResultIndex) |
{ |
var fileTreeElement = new TreeElement("", null, true); |
- fileTreeElement.expanded = expanded; |
fileTreeElement.toggleOnClick = true; |
fileTreeElement.selectable = false; |
@@ -428,6 +602,14 @@ |
fileTreeElement.listItemElement.appendChild(matchesCountSpan); |
+ var searchResult = this._searchResults[searchResultIndex]; |
+ fileTreeElement.onexpand = this._fileTreeElementExpanded.bind(this, searchResult); |
+ |
+ // Expand until at least certain amount of matches is expanded. |
+ if (this._matchesExpandedCount < WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount) |
+ fileTreeElement.expand(); |
+ this._matchesExpandedCount += searchResult.searchMatches.length; |
+ |
return fileTreeElement; |
}, |
@@ -442,7 +624,7 @@ |
var match; |
var offset = 0; |
var matchRanges = []; |
- while (match = regex.exec(lineContent)) |
+ while ((regex.lastIndex < lineContent.length) && (match = regex.exec(lineContent))) |
matchRanges.push({ offset: match.index, length: match[0].length }); |
return matchRanges; |