blob: c7126b4f749406568e6bb4e472b8fcee448c1223 [file] [log] [blame]
// 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 default class SearchResultsPane extends UI.VBox {
/**
* @param {!Search.SearchConfig} searchConfig
*/
constructor(searchConfig) {
super(true);
this._searchConfig = searchConfig;
/** @type {!Array<!Search.SearchResult>} */
this._searchResults = [];
this._treeOutline = new UI.TreeOutlineInShadow();
this._treeOutline.hideOverflow();
this._treeOutline.registerRequiredCSS('search/searchResultsPane.css');
this.contentElement.appendChild(this._treeOutline.element);
this._matchesExpandedCount = 0;
}
/**
* @param {!Search.SearchResult} searchResult
*/
addSearchResult(searchResult) {
this._searchResults.push(searchResult);
this._addTreeElement(searchResult);
}
/**
* @param {!Search.SearchResult} searchResult
*/
_addTreeElement(searchResult) {
const treeElement = new Search.SearchResultsPane.SearchResultsTreeElement(this._searchConfig, searchResult);
this._treeOutline.appendChild(treeElement);
if (!this._treeOutline.selectedTreeElement) {
treeElement.select(/* omitFocus */ true, /* selectedByUser */ true);
}
// Expand until at least a certain number of matches is expanded.
if (this._matchesExpandedCount < Search.SearchResultsPane._matchesExpandedByDefault) {
treeElement.expand();
}
this._matchesExpandedCount += searchResult.matchesCount();
}
}
export const _matchesExpandedByDefault = 20;
export const _matchesShownAtOnce = 20;
export class SearchResultsTreeElement extends UI.TreeElement {
/**
* @param {!Search.SearchConfig} searchConfig
* @param {!Search.SearchResult} searchResult
*/
constructor(searchConfig, searchResult) {
super('', true);
this._searchConfig = searchConfig;
this._searchResult = searchResult;
this._initialized = false;
this.toggleOnClick = true;
}
/**
* @override
*/
onexpand() {
if (this._initialized) {
return;
}
this._updateMatchesUI();
this._initialized = true;
}
_updateMatchesUI() {
this.removeChildren();
const toIndex = Math.min(this._searchResult.matchesCount(), Search.SearchResultsPane._matchesShownAtOnce);
if (toIndex < this._searchResult.matchesCount()) {
this._appendSearchMatches(0, toIndex - 1);
this._appendShowMoreMatchesElement(toIndex - 1);
} else {
this._appendSearchMatches(0, toIndex);
}
}
/**
* @override
*/
onattach() {
this._updateSearchMatches();
}
_updateSearchMatches() {
this.listItemElement.classList.add('search-result');
const fileNameSpan = span(this._searchResult.label(), 'search-result-file-name');
fileNameSpan.appendChild(span('\u2014', 'search-result-dash'));
fileNameSpan.appendChild(span(this._searchResult.description(), 'search-result-qualifier'));
this.tooltip = this._searchResult.description();
this.listItemElement.appendChild(fileNameSpan);
const matchesCountSpan = createElement('span');
matchesCountSpan.className = 'search-result-matches-count';
matchesCountSpan.textContent = `${this._searchResult.matchesCount()}`;
UI.ARIAUtils.setAccessibleName(matchesCountSpan, ls`Matches Count ${this._searchResult.matchesCount()}`);
this.listItemElement.appendChild(matchesCountSpan);
if (this.expanded) {
this._updateMatchesUI();
}
/**
* @param {string} text
* @param {string} className
* @return {!Element}
*/
function span(text, className) {
const span = createElement('span');
span.className = className;
span.textContent = text;
return span;
}
}
/**
* @param {number} fromIndex
* @param {number} toIndex
*/
_appendSearchMatches(fromIndex, toIndex) {
const searchResult = this._searchResult;
const queries = this._searchConfig.queries();
const regexes = [];
for (let i = 0; i < queries.length; ++i) {
regexes.push(createSearchRegex(queries[i], !this._searchConfig.ignoreCase(), this._searchConfig.isRegex()));
}
for (let i = fromIndex; i < toIndex; ++i) {
const lineContent = searchResult.matchLineContent(i).trim();
let matchRanges = [];
for (let j = 0; j < regexes.length; ++j) {
matchRanges = matchRanges.concat(this._regexMatchRanges(lineContent, regexes[j]));
}
const anchor = Components.Linkifier.linkifyRevealable(searchResult.matchRevealable(i), '');
anchor.classList.add('search-match-link');
const labelSpan = createElement('span');
labelSpan.classList.add('search-match-line-number');
const resultLabel = searchResult.matchLabel(i);
labelSpan.textContent = resultLabel;
if (typeof resultLabel === 'number' && !isNaN(resultLabel)) {
UI.ARIAUtils.setAccessibleName(labelSpan, ls`Line ${resultLabel}`);
} else {
UI.ARIAUtils.setAccessibleName(labelSpan, ls`${resultLabel}`);
}
anchor.appendChild(labelSpan);
const contentSpan = this._createContentSpan(lineContent, matchRanges);
anchor.appendChild(contentSpan);
const searchMatchElement = new UI.TreeElement();
this.appendChild(searchMatchElement);
searchMatchElement.listItemElement.className = 'search-match';
searchMatchElement.listItemElement.appendChild(anchor);
searchMatchElement.listItemElement.addEventListener('keydown', event => {
if (isEnterKey(event)) {
event.consume(true);
Common.Revealer.reveal(searchResult.matchRevealable(i));
}
});
searchMatchElement.tooltip = lineContent;
}
}
/**
* @param {number} startMatchIndex
*/
_appendShowMoreMatchesElement(startMatchIndex) {
const matchesLeftCount = this._searchResult.matchesCount() - startMatchIndex;
const showMoreMatchesText = Common.UIString('Show %d more', matchesLeftCount);
const showMoreMatchesTreeElement = new UI.TreeElement(showMoreMatchesText);
this.appendChild(showMoreMatchesTreeElement);
showMoreMatchesTreeElement.listItemElement.classList.add('show-more-matches');
showMoreMatchesTreeElement.onselect =
this._showMoreMatchesElementSelected.bind(this, showMoreMatchesTreeElement, startMatchIndex);
}
/**
* @param {string} lineContent
* @param {!Array.<!TextUtils.SourceRange>} matchRanges
* @return {!Element}
*/
_createContentSpan(lineContent, matchRanges) {
let trimBy = 0;
if (matchRanges.length > 0 && matchRanges[0].offset > 20) {
trimBy = 15;
}
lineContent = lineContent.substring(trimBy, 1000 + trimBy);
if (trimBy) {
matchRanges = matchRanges.map(range => new TextUtils.SourceRange(range.offset - trimBy + 1, range.length));
lineContent = '\u2026' + lineContent;
}
const contentSpan = createElement('span');
contentSpan.className = 'search-match-content';
contentSpan.textContent = lineContent;
UI.ARIAUtils.setAccessibleName(contentSpan, `${lineContent} line`);
UI.highlightRangesWithStyleClass(contentSpan, matchRanges, 'highlighted-match');
return contentSpan;
}
/**
* @param {string} lineContent
* @param {!RegExp} regex
* @return {!Array.<!TextUtils.SourceRange>}
*/
_regexMatchRanges(lineContent, regex) {
regex.lastIndex = 0;
let match;
const matchRanges = [];
while ((regex.lastIndex < lineContent.length) && (match = regex.exec(lineContent))) {
matchRanges.push(new TextUtils.SourceRange(match.index, match[0].length));
}
return matchRanges;
}
/**
* @param {!UI.TreeElement} showMoreMatchesTreeElement
* @param {number} startMatchIndex
* @return {boolean}
*/
_showMoreMatchesElementSelected(showMoreMatchesTreeElement, startMatchIndex) {
this.removeChild(showMoreMatchesTreeElement);
this._appendSearchMatches(startMatchIndex, this._searchResult.matchesCount());
return false;
}
}
/* Legacy exported object */
self.Search = self.Search || {};
/* Legacy exported object */
Search = Search || {};
/**
* @constructor
*/
Search.SearchResultsPane = SearchResultsPane;
Search.SearchResultsPane._matchesExpandedByDefault = _matchesExpandedByDefault;
Search.SearchResultsPane._matchesShownAtOnce = _matchesShownAtOnce;
/**
* @constructor
*/
Search.SearchResultsPane.SearchResultsTreeElement = SearchResultsTreeElement;