1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @constructor
7 * @implements {WebInspector.ProjectSearchConfig}
8 * @param {string} query
9 * @param {boolean} ignoreCase
10 * @param {boolean} isRegex
11 */
12WebInspector.SearchConfig = function(query, ignoreCase, isRegex)
13{
14    this._query = query;
15    this._ignoreCase = ignoreCase;
16    this._isRegex = isRegex;
17    this._parse();
18}
19
20/** @typedef {!{regex: !RegExp, isNegative: boolean}} */
21WebInspector.SearchConfig.RegexQuery;
22
23/**
24 * @param {{query: string, ignoreCase: boolean, isRegex: boolean}} object
25 * @return {!WebInspector.SearchConfig}
26 */
27WebInspector.SearchConfig.fromPlainObject = function(object)
28{
29    return new WebInspector.SearchConfig(object.query, object.ignoreCase, object.isRegex);
30}
31
32WebInspector.SearchConfig.prototype = {
33    /**
34     * @return {string}
35     */
36    query: function()
37    {
38        return this._query;
39    },
40
41    /**
42     * @return {boolean}
43     */
44    ignoreCase: function()
45    {
46        return this._ignoreCase;
47    },
48
49    /**
50     * @return {boolean}
51     */
52    isRegex: function()
53    {
54        return this._isRegex;
55    },
56
57    /**
58     * @return {{query: string, ignoreCase: boolean, isRegex: boolean}}
59     */
60    toPlainObject: function()
61    {
62        return { query: this.query(), ignoreCase: this.ignoreCase(), isRegex: this.isRegex() };
63    },
64
65    _parse: function()
66    {
67        var filePattern = "(?:-)?file:(([^\\\\ ]|\\\\.)+)"; // After file: prefix: any symbol except space and backslash or any symbol escaped with a backslash.
68        var quotedPattern = "\"(([^\\\\\"]|\\\\.)+)\""; // Inside double quotes: any symbol except double quote and backslash or any symbol escaped with a backslash.
69        var unquotedPattern = "(([^\\\\ ]|\\\\.)+)"; // any symbol except space and backslash or any symbol escaped with a backslash.
70
71        var pattern = "(" + filePattern + ")|(" + quotedPattern + ")|(" + unquotedPattern + ")";
72        var regexp = new RegExp(pattern, "g");
73        var queryParts = this._query.match(regexp) || [];
74
75        /**
76         * @type {!Array.<!WebInspector.SearchConfig.QueryTerm>}
77         */
78        this._fileQueries = [];
79
80        /**
81         * @type {!Array.<string>}
82         */
83        this._queries = [];
84
85        for (var i = 0; i < queryParts.length; ++i) {
86            var queryPart = queryParts[i];
87            if (!queryPart)
88                continue;
89            var fileQuery = this._parseFileQuery(queryPart);
90            if (fileQuery) {
91                this._fileQueries.push(fileQuery);
92                /** @type {!Array.<!WebInspector.SearchConfig.RegexQuery>} */
93                this._fileRegexQueries = this._fileRegexQueries || [];
94                this._fileRegexQueries.push({ regex: new RegExp(fileQuery.text, this.ignoreCase ? "i" : ""), isNegative: fileQuery.isNegative });
95                continue;
96            }
97            if (queryPart.startsWith("\"")) {
98                if (!queryPart.endsWith("\""))
99                    continue;
100                this._queries.push(this._parseQuotedQuery(queryPart));
101                continue;
102            }
103            this._queries.push(this._parseUnquotedQuery(queryPart));
104        }
105    },
106
107    /**
108     * @param {string} filePath
109     * @return {boolean}
110     */
111    filePathMatchesFileQuery: function(filePath)
112    {
113        if (!this._fileRegexQueries)
114            return true;
115        for (var i = 0; i < this._fileRegexQueries.length; ++i) {
116            if (!!filePath.match(this._fileRegexQueries[i].regex) === this._fileRegexQueries[i].isNegative)
117                return false;
118        }
119        return true;
120    },
121
122    /**
123     * @return {!Array.<string>}
124     */
125    queries: function()
126    {
127        return this._queries;
128    },
129
130    _parseUnquotedQuery: function(query)
131    {
132        return query.replace(/\\(.)/g, "$1");
133    },
134
135    _parseQuotedQuery: function(query)
136    {
137        return query.substring(1, query.length - 1).replace(/\\(.)/g, "$1");
138    },
139
140    /**
141     * @param {string} query
142     * @return {?WebInspector.SearchConfig.QueryTerm}
143     */
144    _parseFileQuery: function(query)
145    {
146        var match = query.match(/^(-)?file:/);
147        if (!match)
148            return null;
149        var isNegative = !!match[1];
150        query = query.substr(match[0].length);
151        var result = "";
152        for (var i = 0; i < query.length; ++i) {
153            var char = query[i];
154            if (char === "*") {
155                result += ".*";
156            } else if (char === "\\") {
157                ++i;
158                var nextChar = query[i];
159                if (nextChar === " ")
160                    result += " ";
161            } else {
162                if (String.regexSpecialCharacters().indexOf(query.charAt(i)) !== -1)
163                    result += "\\";
164                result += query.charAt(i);
165            }
166        }
167        return new WebInspector.SearchConfig.QueryTerm(result, isNegative);
168    }
169}
170
171/**
172 * @constructor
173 * @param {string} text
174 * @param {boolean} isNegative
175 */
176WebInspector.SearchConfig.QueryTerm = function(text, isNegative)
177{
178    this.text = text;
179    this.isNegative = isNegative;
180}
181