blob: 45d032e3db042ff9a7eb80d5e9c08db78d2d3583 [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.
/**
* @implements {Workspace.ProjectSearchConfig}
*/
export class SearchConfig {
/**
* @param {string} query
* @param {boolean} ignoreCase
* @param {boolean} isRegex
*/
constructor(query, ignoreCase, isRegex) {
this._query = query;
this._ignoreCase = ignoreCase;
this._isRegex = isRegex;
this._parse();
}
/**
* @param {{query: string, ignoreCase: boolean, isRegex: boolean}} object
* @return {!Search.SearchConfig}
*/
static fromPlainObject(object) {
return new SearchConfig(object.query, object.ignoreCase, object.isRegex);
}
/**
* @override
* @return {string}
*/
query() {
return this._query;
}
/**
* @override
* @return {boolean}
*/
ignoreCase() {
return this._ignoreCase;
}
/**
* @override
* @return {boolean}
*/
isRegex() {
return this._isRegex;
}
/**
* @return {{query: string, ignoreCase: boolean, isRegex: boolean}}
*/
toPlainObject() {
return {query: this.query(), ignoreCase: this.ignoreCase(), isRegex: this.isRegex()};
}
_parse() {
// Inside double quotes: any symbol except double quote and backslash or any symbol escaped with a backslash.
const quotedPattern = /"([^\\"]|\\.)+"/;
// A word is a sequence of any symbols except space and backslash or any symbols escaped with a backslash, that does not start with file:.
const unquotedWordPattern = /(\s*(?!-?f(ile)?:)[^\\ ]|\\.)+/;
const unquotedPattern = unquotedWordPattern.source + '(\\s+' + unquotedWordPattern.source + ')*';
const pattern = [
'(\\s*' + FilePatternRegex.source + '\\s*)',
'(' + quotedPattern.source + ')',
'(' + unquotedPattern + ')',
].join('|');
const regexp = new RegExp(pattern, 'g');
const queryParts = this._query.match(regexp) || [];
/**
* @type {!Array.<!Search.SearchConfig.QueryTerm>}
*/
this._fileQueries = [];
/**
* @type {!Array.<string>}
*/
this._queries = [];
for (let i = 0; i < queryParts.length; ++i) {
const queryPart = queryParts[i];
if (!queryPart) {
continue;
}
const fileQuery = this._parseFileQuery(queryPart);
if (fileQuery) {
this._fileQueries.push(fileQuery);
/** @type {!Array.<!Search.SearchConfig.RegexQuery>} */
this._fileRegexQueries = this._fileRegexQueries || [];
this._fileRegexQueries.push(
{regex: new RegExp(fileQuery.text, this.ignoreCase ? 'i' : ''), isNegative: fileQuery.isNegative});
continue;
}
if (this._isRegex) {
this._queries.push(queryPart);
continue;
}
if (queryPart.startsWith('"')) {
if (!queryPart.endsWith('"')) {
continue;
}
this._queries.push(this._parseQuotedQuery(queryPart));
continue;
}
this._queries.push(this._parseUnquotedQuery(queryPart));
}
}
/**
* @override
* @param {string} filePath
* @return {boolean}
*/
filePathMatchesFileQuery(filePath) {
if (!this._fileRegexQueries) {
return true;
}
for (let i = 0; i < this._fileRegexQueries.length; ++i) {
if (!!filePath.match(this._fileRegexQueries[i].regex) === this._fileRegexQueries[i].isNegative) {
return false;
}
}
return true;
}
/**
* @override
* @return {!Array.<string>}
*/
queries() {
return this._queries;
}
_parseUnquotedQuery(query) {
return query.replace(/\\(.)/g, '$1');
}
_parseQuotedQuery(query) {
return query.substring(1, query.length - 1).replace(/\\(.)/g, '$1');
}
/**
* @param {string} query
* @return {?Search.SearchConfig.QueryTerm}
*/
_parseFileQuery(query) {
const match = query.match(FilePatternRegex);
if (!match) {
return null;
}
const isNegative = !!match[1];
query = match[3];
let result = '';
for (let i = 0; i < query.length; ++i) {
const char = query[i];
if (char === '*') {
result += '.*';
} else if (char === '\\') {
++i;
const nextChar = query[i];
if (nextChar === ' ') {
result += ' ';
}
} else {
if (String.regexSpecialCharacters().indexOf(query.charAt(i)) !== -1) {
result += '\\';
}
result += query.charAt(i);
}
}
return new QueryTerm(result, isNegative);
}
}
// After file: prefix: any symbol except space and backslash or any symbol escaped with a backslash.
export const FilePatternRegex = /(-)?f(ile)?:((?:[^\\ ]|\\.)+)/;
export class QueryTerm {
/**
* @param {string} text
* @param {boolean} isNegative
*/
constructor(text, isNegative) {
this.text = text;
this.isNegative = isNegative;
}
}
/**
* @interface
*/
export class SearchResult {
/**
* @return {string}
*/
label() {
}
/**
* @return {string}
*/
description() {
}
/**
* @return {number}
*/
matchesCount() {
}
/**
* @param {number} index
* @return {string}
*/
matchLabel(index) {
}
/**
* @param {number} index
* @return {string}
*/
matchLineContent(index) {
}
/**
* @param {number} index
* @return {!Object}
*/
matchRevealable(index) {}
}
/**
* @interface
*/
export class SearchScope {
/**
* @param {!Search.SearchConfig} searchConfig
* @param {!Common.Progress} progress
* @param {function(!Search.SearchResult)} searchResultCallback
* @param {function(boolean)} searchFinishedCallback
*/
performSearch(searchConfig, progress, searchResultCallback, searchFinishedCallback) {
}
/**
* @param {!Common.Progress} progress
*/
performIndexing(progress) {
}
stopSearch() {}
}
/* Legacy exported object */
self.Search = self.Search || {};
/* Legacy exported object */
Search = Search || {};
/**
* @constructor
*/
Search.SearchConfig = SearchConfig;
/** @typedef {!{regex: !RegExp, isNegative: boolean}} */
Search.SearchConfig.RegexQuery;
Search.SearchConfig.FilePatternRegex = FilePatternRegex;
/**
* @constructor
*/
Search.SearchConfig.QueryTerm = QueryTerm;
/**
* @interface
*/
Search.SearchResult = SearchResult;
/**
* @interface
*/
Search.SearchScope = SearchScope;