| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| export class SearchView extends UI.VBox { |
| /** |
| * @param {string} settingKey |
| */ |
| constructor(settingKey) { |
| super(true); |
| this.setMinimumSize(0, 40); |
| this.registerRequiredCSS('search/searchView.css'); |
| |
| this._focusOnShow = false; |
| this._isIndexing = false; |
| this._searchId = 1; |
| this._searchMatchesCount = 0; |
| this._searchResultsCount = 0; |
| this._nonEmptySearchResultsCount = 0; |
| /** @type {?UI.Widget} */ |
| this._searchingView = null; |
| /** @type {?UI.Widget} */ |
| this._notFoundView = null; |
| /** @type {?Search.SearchConfig} */ |
| this._searchConfig = null; |
| /** @type {?Search.SearchConfig} */ |
| this._pendingSearchConfig = null; |
| /** @type {?Search.SearchResultsPane} */ |
| this._searchResultsPane = null; |
| /** @type {?UI.ProgressIndicator} */ |
| this._progressIndicator = null; |
| /** @type {?UI.Widget} */ |
| this._visiblePane = null; |
| |
| this.contentElement.classList.add('search-view'); |
| |
| this._searchPanelElement = this.contentElement.createChild('div', 'search-drawer-header'); |
| this._searchPanelElement.addEventListener('keydown', this._onKeyDown.bind(this), false); |
| |
| this._searchResultsElement = this.contentElement.createChild('div'); |
| this._searchResultsElement.className = 'search-results'; |
| |
| const searchContainer = createElement('div'); |
| searchContainer.style.flex = 'auto'; |
| searchContainer.style.justifyContent = 'start'; |
| searchContainer.style.maxWidth = '300px'; |
| this._search = UI.HistoryInput.create(); |
| searchContainer.appendChild(this._search); |
| this._search.placeholder = Common.UIString('Search'); |
| this._search.setAttribute('type', 'text'); |
| this._search.setAttribute('results', '0'); |
| this._search.setAttribute('size', 42); |
| UI.ARIAUtils.setAccessibleName(this._search, ls`Search Query`); |
| const searchItem = new UI.ToolbarItem(searchContainer); |
| |
| const toolbar = new UI.Toolbar('search-toolbar', this._searchPanelElement); |
| this._matchCaseButton = Search.SearchView._appendToolbarToggle(toolbar, 'Aa', Common.UIString('Match Case')); |
| this._regexButton = |
| Search.SearchView._appendToolbarToggle(toolbar, '.*', Common.UIString('Use Regular Expression')); |
| toolbar.appendToolbarItem(searchItem); |
| const refreshButton = new UI.ToolbarButton(Common.UIString('Refresh'), 'largeicon-refresh'); |
| const clearButton = new UI.ToolbarButton(Common.UIString('Clear'), 'largeicon-clear'); |
| toolbar.appendToolbarItem(refreshButton); |
| toolbar.appendToolbarItem(clearButton); |
| refreshButton.addEventListener(UI.ToolbarButton.Events.Click, this._onAction.bind(this)); |
| clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => { |
| this._resetSearch(); |
| this._onSearchInputClear(); |
| }); |
| |
| const searchStatusBarElement = this.contentElement.createChild('div', 'search-toolbar-summary'); |
| this._searchMessageElement = searchStatusBarElement.createChild('div', 'search-message'); |
| this._searchProgressPlaceholderElement = searchStatusBarElement.createChild('div', 'flex-centered'); |
| this._searchResultsMessageElement = searchStatusBarElement.createChild('div', 'search-message'); |
| |
| this._advancedSearchConfig = Common.settings.createLocalSetting( |
| settingKey + 'SearchConfig', new Search.SearchConfig('', true, false).toPlainObject()); |
| |
| this._load(); |
| /** @type {?Search.SearchScope} */ |
| this._searchScope = null; |
| } |
| |
| /** |
| * @param {!UI.Toolbar} toolbar |
| * @param {string} text |
| * @param {string} tooltip |
| * @return {!UI.ToolbarToggle} |
| */ |
| static _appendToolbarToggle(toolbar, text, tooltip) { |
| const toggle = new UI.ToolbarToggle(tooltip); |
| toggle.setText(text); |
| toggle.addEventListener(UI.ToolbarButton.Events.Click, () => toggle.setToggled(!toggle.toggled())); |
| toolbar.appendToolbarItem(toggle); |
| return toggle; |
| } |
| |
| /** |
| * @return {!Search.SearchConfig} |
| */ |
| _buildSearchConfig() { |
| return new Search.SearchConfig(this._search.value, !this._matchCaseButton.toggled(), this._regexButton.toggled()); |
| } |
| |
| /** |
| * @protected |
| * @param {string} queryCandidate |
| * @param {boolean=} searchImmediately |
| */ |
| async toggle(queryCandidate, searchImmediately) { |
| if (queryCandidate) { |
| this._search.value = queryCandidate; |
| } |
| if (this.isShowing()) { |
| this.focus(); |
| } else { |
| this._focusOnShow = true; |
| } |
| |
| this._initScope(); |
| if (searchImmediately) { |
| this._onAction(); |
| } else { |
| this._startIndexing(); |
| } |
| } |
| |
| /** |
| * @protected |
| * @return {!Search.SearchScope} |
| */ |
| createScope() { |
| throw new Error('Not implemented'); |
| } |
| |
| _initScope() { |
| this._searchScope = this.createScope(); |
| } |
| |
| /** |
| * @override |
| */ |
| wasShown() { |
| if (this._focusOnShow) { |
| this.focus(); |
| this._focusOnShow = false; |
| } |
| } |
| |
| _onIndexingFinished() { |
| const finished = !this._progressIndicator.isCanceled(); |
| this._progressIndicator.done(); |
| this._progressIndicator = null; |
| this._isIndexing = false; |
| this._indexingFinished(finished); |
| if (!finished) { |
| this._pendingSearchConfig = null; |
| } |
| if (!this._pendingSearchConfig) { |
| return; |
| } |
| const searchConfig = this._pendingSearchConfig; |
| this._pendingSearchConfig = null; |
| this._innerStartSearch(searchConfig); |
| } |
| |
| _startIndexing() { |
| this._isIndexing = true; |
| if (this._progressIndicator) { |
| this._progressIndicator.done(); |
| } |
| this._progressIndicator = new UI.ProgressIndicator(); |
| this._searchMessageElement.textContent = Common.UIString('Indexing\u2026'); |
| this._progressIndicator.show(this._searchProgressPlaceholderElement); |
| this._searchScope.performIndexing( |
| new Common.ProgressProxy(this._progressIndicator, this._onIndexingFinished.bind(this))); |
| } |
| |
| _onSearchInputClear() { |
| this._search.value = ''; |
| this.focus(); |
| } |
| |
| /** |
| * @param {number} searchId |
| * @param {!Search.SearchResult} searchResult |
| */ |
| _onSearchResult(searchId, searchResult) { |
| if (searchId !== this._searchId || !this._progressIndicator) { |
| return; |
| } |
| if (this._progressIndicator && this._progressIndicator.isCanceled()) { |
| this._onIndexingFinished(); |
| return; |
| } |
| this._addSearchResult(searchResult); |
| if (!searchResult.matchesCount()) { |
| return; |
| } |
| if (!this._searchResultsPane) { |
| this._searchResultsPane = new Search.SearchResultsPane(/** @type {!Search.SearchConfig} */ (this._searchConfig)); |
| this._showPane(this._searchResultsPane); |
| } |
| this._searchResultsPane.addSearchResult(searchResult); |
| } |
| |
| /** |
| * @param {number} searchId |
| * @param {boolean} finished |
| */ |
| _onSearchFinished(searchId, finished) { |
| if (searchId !== this._searchId || !this._progressIndicator) { |
| return; |
| } |
| if (!this._searchResultsPane) { |
| this._nothingFound(); |
| } |
| this._searchFinished(finished); |
| this._searchConfig = null; |
| UI.ARIAUtils.alert( |
| this._searchMessageElement.textContent + ' ' + this._searchResultsMessageElement.textContent, |
| this._searchMessageElement); |
| } |
| |
| /** |
| * @param {!Search.SearchConfig} searchConfig |
| */ |
| async _startSearch(searchConfig) { |
| this._resetSearch(); |
| ++this._searchId; |
| this._initScope(); |
| if (!this._isIndexing) { |
| this._startIndexing(); |
| } |
| this._pendingSearchConfig = searchConfig; |
| } |
| |
| _innerStartSearch(searchConfig) { |
| this._searchConfig = searchConfig; |
| if (this._progressIndicator) { |
| this._progressIndicator.done(); |
| } |
| this._progressIndicator = new UI.ProgressIndicator(); |
| this._searchStarted(this._progressIndicator); |
| this._searchScope.performSearch( |
| searchConfig, this._progressIndicator, this._onSearchResult.bind(this, this._searchId), |
| this._onSearchFinished.bind(this, this._searchId)); |
| } |
| |
| _resetSearch() { |
| this._stopSearch(); |
| this._showPane(null); |
| this._searchResultsPane = null; |
| } |
| |
| _stopSearch() { |
| if (this._progressIndicator && !this._isIndexing) { |
| this._progressIndicator.cancel(); |
| } |
| if (this._searchScope) { |
| this._searchScope.stopSearch(); |
| } |
| this._searchConfig = null; |
| } |
| |
| /** |
| * @param {!UI.ProgressIndicator} progressIndicator |
| */ |
| _searchStarted(progressIndicator) { |
| this._resetCounters(); |
| if (!this._searchingView) { |
| this._searchingView = new UI.EmptyWidget(Common.UIString('Searching\u2026')); |
| } |
| this._showPane(this._searchingView); |
| this._searchMessageElement.textContent = Common.UIString('Searching\u2026'); |
| progressIndicator.show(this._searchProgressPlaceholderElement); |
| this._updateSearchResultsMessage(); |
| } |
| |
| /** |
| * @param {boolean} finished |
| */ |
| _indexingFinished(finished) { |
| this._searchMessageElement.textContent = finished ? '' : Common.UIString('Indexing interrupted.'); |
| } |
| |
| _updateSearchResultsMessage() { |
| if (this._searchMatchesCount && this._searchResultsCount) { |
| if (this._searchMatchesCount === 1 && this._nonEmptySearchResultsCount === 1) { |
| this._searchResultsMessageElement.textContent = Common.UIString('Found 1 matching line in 1 file.'); |
| } else if (this._searchMatchesCount > 1 && this._nonEmptySearchResultsCount === 1) { |
| this._searchResultsMessageElement.textContent = |
| Common.UIString('Found %d matching lines in 1 file.', this._searchMatchesCount); |
| } else { |
| this._searchResultsMessageElement.textContent = Common.UIString( |
| 'Found %d matching lines in %d files.', this._searchMatchesCount, this._nonEmptySearchResultsCount); |
| } |
| } else { |
| this._searchResultsMessageElement.textContent = ''; |
| } |
| } |
| |
| /** |
| * @param {?UI.Widget} panel |
| */ |
| _showPane(panel) { |
| if (this._visiblePane) { |
| this._visiblePane.detach(); |
| } |
| if (panel) { |
| panel.show(this._searchResultsElement); |
| } |
| this._visiblePane = panel; |
| } |
| |
| _resetCounters() { |
| this._searchMatchesCount = 0; |
| this._searchResultsCount = 0; |
| this._nonEmptySearchResultsCount = 0; |
| } |
| |
| _nothingFound() { |
| if (!this._notFoundView) { |
| this._notFoundView = new UI.EmptyWidget(Common.UIString('No matches found.')); |
| } |
| this._showPane(this._notFoundView); |
| this._searchResultsMessageElement.textContent = Common.UIString('No matches found.'); |
| } |
| |
| /** |
| * @param {!Search.SearchResult} searchResult |
| */ |
| _addSearchResult(searchResult) { |
| const matchesCount = searchResult.matchesCount(); |
| this._searchMatchesCount += matchesCount; |
| this._searchResultsCount++; |
| if (matchesCount) { |
| this._nonEmptySearchResultsCount++; |
| } |
| this._updateSearchResultsMessage(); |
| } |
| |
| /** |
| * @param {boolean} finished |
| */ |
| _searchFinished(finished) { |
| this._searchMessageElement.textContent = |
| finished ? Common.UIString('Search finished.') : Common.UIString('Search interrupted.'); |
| } |
| |
| /** |
| * @override |
| */ |
| focus() { |
| this._search.focus(); |
| this._search.select(); |
| } |
| |
| /** |
| * @override |
| */ |
| willHide() { |
| this._stopSearch(); |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| _onKeyDown(event) { |
| switch (event.keyCode) { |
| case UI.KeyboardShortcut.Keys.Enter.code: |
| this._onAction(); |
| break; |
| } |
| } |
| |
| _save() { |
| this._advancedSearchConfig.set(this._buildSearchConfig().toPlainObject()); |
| } |
| |
| _load() { |
| const searchConfig = Search.SearchConfig.fromPlainObject(this._advancedSearchConfig.get()); |
| this._search.value = searchConfig.query(); |
| this._matchCaseButton.setToggled(!searchConfig.ignoreCase()); |
| this._regexButton.setToggled(searchConfig.isRegex()); |
| } |
| |
| _onAction() { |
| // Resetting alert variable to prime for next search query result. |
| UI.ARIAUtils.alert(' ', this._searchMessageElement); |
| const searchConfig = this._buildSearchConfig(); |
| if (!searchConfig.query() || !searchConfig.query().length) { |
| return; |
| } |
| this._save(); |
| this._startSearch(searchConfig); |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.Search = self.Search || {}; |
| |
| /* Legacy exported object */ |
| Search = Search || {}; |
| |
| /** |
| * @constructor |
| */ |
| Search.SearchView = SearchView; |