UNPKG

chrome-devtools-frontend

Version:
493 lines (448 loc) • 15.1 kB
// 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. import * as Common from '../common/common.js'; import * as i18n from '../i18n/i18n.js'; import * as UI from '../ui/ui.js'; import {SearchConfig, SearchResult, SearchScope} from './SearchConfig.js'; // eslint-disable-line no-unused-vars import {SearchResultsPane} from './SearchResultsPane.js'; export const UIStrings = { /** *@description Title of a search bar or tool */ search: 'Search', /** *@description Accessibility label for search query text box */ searchQuery: 'Search Query', /** *@description Text to search by matching case of the input */ matchCase: 'Match Case', /** *@description Text for searching with regular expressinn */ useRegularExpression: 'Use Regular Expression', /** *@description Text to refresh the page */ refresh: 'Refresh', /** *@description Text to clear content */ clear: 'Clear', /** *@description Search message element text content in Search View of the Search tab */ indexing: 'Indexing…', /** *@description Text to indicate the searching is in progress */ searching: 'Searching…', /** *@description Text in Search View of the Search tab */ indexingInterrupted: 'Indexing interrupted.', /** *@description Search results message element text content in Search View of the Search tab */ foundMatchingLineInFile: 'Found 1 matching line in 1 file.', /** *@description Search results message element text content in Search View of the Search tab *@example {2} PH1 */ foundDMatchingLinesInFile: 'Found {PH1} matching lines in 1 file.', /** *@description Search results message element text content in Search View of the Search tab *@example {2} PH1 *@example {2} PH2 */ foundDMatchingLinesInDFiles: 'Found {PH1} matching lines in {PH2} files.', /** *@description Search results message element text content in Search View of the Search tab */ noMatchesFound: 'No matches found.', /** *@description Text in Search View of the Search tab */ searchFinished: 'Search finished.', /** *@description Text in Search View of the Search tab */ searchInterrupted: 'Search interrupted.', }; const str_ = i18n.i18n.registerUIStrings('search/SearchView.js', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class SearchView extends UI.Widget.VBox { /** * @param {string} settingKey */ constructor(settingKey) { super(true); this.setMinimumSize(0, 40); this.registerRequiredCSS('search/searchView.css', {enableLegacyPatching: true}); this._focusOnShow = false; this._isIndexing = false; this._searchId = 1; this._searchMatchesCount = 0; this._searchResultsCount = 0; this._nonEmptySearchResultsCount = 0; /** @type {?UI.Widget.Widget} */ this._searchingView = null; /** @type {?UI.Widget.Widget} */ this._notFoundView = null; /** @type {?SearchConfig} */ this._searchConfig = null; /** @type {?SearchConfig} */ this._pendingSearchConfig = null; /** @type {?SearchResultsPane} */ this._searchResultsPane = null; /** @type {?UI.ProgressIndicator.ProgressIndicator} */ this._progressIndicator = null; /** @type {?UI.Widget.Widget} */ this._visiblePane = null; this.contentElement.classList.add('search-view'); this._searchPanelElement = this.contentElement.createChild('div', 'search-drawer-header'); this._searchResultsElement = this.contentElement.createChild('div'); this._searchResultsElement.className = 'search-results'; const searchContainer = document.createElement('div'); searchContainer.style.flex = 'auto'; searchContainer.style.justifyContent = 'start'; searchContainer.style.maxWidth = '300px'; this._search = UI.HistoryInput.HistoryInput.create(); this._search.addEventListener('keydown', event => { this._onKeyDown(/** @type {!KeyboardEvent} */ (event)); }); searchContainer.appendChild(this._search); this._search.placeholder = i18nString(UIStrings.search); this._search.setAttribute('type', 'text'); this._search.setAttribute('results', '0'); this._search.setAttribute('size', '42'); UI.ARIAUtils.setAccessibleName(this._search, i18nString(UIStrings.searchQuery)); const searchItem = new UI.Toolbar.ToolbarItem(searchContainer); const toolbar = new UI.Toolbar.Toolbar('search-toolbar', this._searchPanelElement); this._matchCaseButton = SearchView._appendToolbarToggle(toolbar, 'Aa', i18nString(UIStrings.matchCase)); this._regexButton = SearchView._appendToolbarToggle(toolbar, '.*', i18nString(UIStrings.useRegularExpression)); toolbar.appendToolbarItem(searchItem); const refreshButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.refresh), 'largeicon-refresh'); const clearButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.clear), 'largeicon-clear'); toolbar.appendToolbarItem(refreshButton); toolbar.appendToolbarItem(clearButton); refreshButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, () => this._onAction()); clearButton.addEventListener(UI.Toolbar.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.Settings.instance().createLocalSetting( settingKey + 'SearchConfig', new SearchConfig('', true, false).toPlainObject()); this._load(); /** @type {?SearchScope} */ this._searchScope = null; } /** * @param {!UI.Toolbar.Toolbar} toolbar * @param {string} text * @param {string} tooltip * @return {!UI.Toolbar.ToolbarToggle} */ static _appendToolbarToggle(toolbar, text, tooltip) { const toggle = new UI.Toolbar.ToolbarToggle(tooltip); toggle.setText(text); toggle.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, () => toggle.setToggled(!toggle.toggled())); toolbar.appendToolbarItem(toggle); return toggle; } /** * @return {!SearchConfig} */ _buildSearchConfig() { return new SearchConfig(this._search.value, !this._matchCaseButton.toggled(), this._regexButton.toggled()); } /** * @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 {!SearchScope} */ createScope() { throw new Error('Not implemented'); } _initScope() { this._searchScope = this.createScope(); } /** * @override */ wasShown() { if (this._focusOnShow) { this.focus(); this._focusOnShow = false; } } _onIndexingFinished() { if (!this._progressIndicator) { return; } 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.ProgressIndicator(); this._searchMessageElement.textContent = i18nString(UIStrings.indexing); this._progressIndicator.show(this._searchProgressPlaceholderElement); if (this._searchScope) { this._searchScope.performIndexing( new Common.Progress.ProgressProxy(this._progressIndicator, this._onIndexingFinished.bind(this))); } } _onSearchInputClear() { this._search.value = ''; this._save(); this.focus(); } /** * @param {number} searchId * @param {!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 SearchResultsPane(/** @type {!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 {!SearchConfig} searchConfig */ async _startSearch(searchConfig) { this._resetSearch(); ++this._searchId; this._initScope(); if (!this._isIndexing) { this._startIndexing(); } this._pendingSearchConfig = searchConfig; } /** * * @param {!SearchConfig} searchConfig */ _innerStartSearch(searchConfig) { this._searchConfig = searchConfig; if (this._progressIndicator) { this._progressIndicator.done(); } this._progressIndicator = new UI.ProgressIndicator.ProgressIndicator(); this._searchStarted(this._progressIndicator); if (this._searchScope) { 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; this._clearSearchMessage(); } _clearSearchMessage() { this._searchMessageElement.textContent = ''; this._searchResultsMessageElement.textContent = ''; } _stopSearch() { if (this._progressIndicator && !this._isIndexing) { this._progressIndicator.cancel(); } if (this._searchScope) { this._searchScope.stopSearch(); } this._searchConfig = null; } /** * @param {!UI.ProgressIndicator.ProgressIndicator} progressIndicator */ _searchStarted(progressIndicator) { this._resetCounters(); if (!this._searchingView) { this._searchingView = new UI.EmptyWidget.EmptyWidget(i18nString(UIStrings.searching)); } this._showPane(this._searchingView); this._searchMessageElement.textContent = i18nString(UIStrings.searching); progressIndicator.show(this._searchProgressPlaceholderElement); this._updateSearchResultsMessage(); } /** * @param {boolean} finished */ _indexingFinished(finished) { this._searchMessageElement.textContent = finished ? '' : i18nString(UIStrings.indexingInterrupted); } _updateSearchResultsMessage() { if (this._searchMatchesCount && this._searchResultsCount) { if (this._searchMatchesCount === 1 && this._nonEmptySearchResultsCount === 1) { this._searchResultsMessageElement.textContent = i18nString(UIStrings.foundMatchingLineInFile); } else if (this._searchMatchesCount > 1 && this._nonEmptySearchResultsCount === 1) { this._searchResultsMessageElement.textContent = i18nString(UIStrings.foundDMatchingLinesInFile, {PH1: this._searchMatchesCount}); } else { this._searchResultsMessageElement.textContent = i18nString( UIStrings.foundDMatchingLinesInDFiles, {PH1: this._searchMatchesCount, PH2: this._nonEmptySearchResultsCount}); } } else { this._searchResultsMessageElement.textContent = ''; } } /** * @param {?UI.Widget.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.EmptyWidget(i18nString(UIStrings.noMatchesFound)); } this._showPane(this._notFoundView); this._searchResultsMessageElement.textContent = i18nString(UIStrings.noMatchesFound); } /** * @param {!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 ? i18nString(UIStrings.searchFinished) : i18nString(UIStrings.searchInterrupted); } /** * @override */ focus() { this._search.focus(); this._search.select(); } /** * @override */ willHide() { this._stopSearch(); } /** * @param {!KeyboardEvent} event */ _onKeyDown(event) { this._save(); switch (event.keyCode) { case UI.KeyboardShortcut.Keys.Enter.code: this._onAction(); break; } } _save() { this._advancedSearchConfig.set(this._buildSearchConfig().toPlainObject()); } _load() { const searchConfig = 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._startSearch(searchConfig); } }