blob: 7dadf6ccc970a2ba2bce74f44eaaaa775406b274 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28/**
29 * @implements {Search.SearchScope}
30 */
31Sources.SourcesSearchScope = class {
32 constructor() {
33 // FIXME: Add title once it is used by search controller.
34 this._searchId = 0;
35 /** @type {!Array<!Workspace.UISourceCode>} */
36 this._searchResultCandidates = [];
37 /** @type {?function(!Search.SearchResult)} */
38 this._searchResultCallback = null;
39 /** @type {?function(boolean)} */
40 this._searchFinishedCallback = null;
41 /** @type {?Workspace.ProjectSearchConfig} */
42 this._searchConfig = null;
43 }
44
45 /**
46 * @param {!Workspace.UISourceCode} uiSourceCode1
47 * @param {!Workspace.UISourceCode} uiSourceCode2
48 * @return {number}
49 */
50 static _filesComparator(uiSourceCode1, uiSourceCode2) {
51 if (uiSourceCode1.isDirty() && !uiSourceCode2.isDirty())
52 return -1;
53 if (!uiSourceCode1.isDirty() && uiSourceCode2.isDirty())
54 return 1;
Joel Einbinderef3555d2018-12-04 02:14:4455 const isFileSystem1 = uiSourceCode1.project().type() === Workspace.projectTypes.FileSystem &&
56 !Persistence.persistence.binding(uiSourceCode1);
57 const isFileSystem2 = uiSourceCode2.project().type() === Workspace.projectTypes.FileSystem &&
58 !Persistence.persistence.binding(uiSourceCode2);
59 if (isFileSystem1 !== isFileSystem2)
60 return isFileSystem1 ? 1 : -1;
Blink Reformat4c46d092018-04-07 15:32:3761 const url1 = uiSourceCode1.url();
62 const url2 = uiSourceCode2.url();
63 if (url1 && !url2)
64 return -1;
65 if (!url1 && url2)
66 return 1;
67 return String.naturalOrderComparator(uiSourceCode1.fullDisplayName(), uiSourceCode2.fullDisplayName());
68 }
69
70 /**
71 * @override
72 * @param {!Common.Progress} progress
73 */
74 performIndexing(progress) {
75 this.stopSearch();
76
77 const projects = this._projects();
78 const compositeProgress = new Common.CompositeProgress(progress);
79 for (let i = 0; i < projects.length; ++i) {
80 const project = projects[i];
81 const projectProgress = compositeProgress.createSubProgress(project.uiSourceCodes().length);
82 project.indexContent(projectProgress);
83 }
84 }
85
86 /**
87 * @return {!Array.<!Workspace.Project>}
88 */
89 _projects() {
90 const searchInAnonymousAndContentScripts = Common.moduleSetting('searchInAnonymousAndContentScripts').get();
91
92 return Workspace.workspace.projects().filter(project => {
93 if (project.type() === Workspace.projectTypes.Service)
94 return false;
95 if (!searchInAnonymousAndContentScripts && project.isServiceProject())
96 return false;
97 if (!searchInAnonymousAndContentScripts && project.type() === Workspace.projectTypes.ContentScripts)
98 return false;
99 return true;
100 });
101 }
102
103 /**
104 * @override
105 * @param {!Workspace.ProjectSearchConfig} searchConfig
106 * @param {!Common.Progress} progress
107 * @param {function(!Search.SearchResult)} searchResultCallback
108 * @param {function(boolean)} searchFinishedCallback
109 */
110 performSearch(searchConfig, progress, searchResultCallback, searchFinishedCallback) {
111 this.stopSearch();
112 this._searchResultCandidates = [];
113 this._searchResultCallback = searchResultCallback;
114 this._searchFinishedCallback = searchFinishedCallback;
115 this._searchConfig = searchConfig;
116
117 const promises = [];
118 const compositeProgress = new Common.CompositeProgress(progress);
119 const searchContentProgress = compositeProgress.createSubProgress();
120 const findMatchingFilesProgress = new Common.CompositeProgress(compositeProgress.createSubProgress());
121 for (const project of this._projects()) {
122 const weight = project.uiSourceCodes().length;
123 const findMatchingFilesInProjectProgress = findMatchingFilesProgress.createSubProgress(weight);
124 const filesMathingFileQuery = this._projectFilesMatchingFileQuery(project, searchConfig);
125 const promise =
126 project
127 .findFilesMatchingSearchRequest(searchConfig, filesMathingFileQuery, findMatchingFilesInProjectProgress)
128 .then(this._processMatchingFilesForProject.bind(
129 this, this._searchId, project, searchConfig, filesMathingFileQuery));
130 promises.push(promise);
131 }
132
133 Promise.all(promises).then(this._processMatchingFiles.bind(
134 this, this._searchId, searchContentProgress, this._searchFinishedCallback.bind(this, true)));
135 }
136
137 /**
138 * @param {!Workspace.Project} project
139 * @param {!Workspace.ProjectSearchConfig} searchConfig
140 * @param {boolean=} dirtyOnly
141 * @return {!Array.<string>}
142 */
143 _projectFilesMatchingFileQuery(project, searchConfig, dirtyOnly) {
144 const result = [];
145 const uiSourceCodes = project.uiSourceCodes();
146 for (let i = 0; i < uiSourceCodes.length; ++i) {
147 const uiSourceCode = uiSourceCodes[i];
148 if (!uiSourceCode.contentType().isTextType())
149 continue;
150 const binding = Persistence.persistence.binding(uiSourceCode);
151 if (binding && binding.network === uiSourceCode)
152 continue;
153 if (dirtyOnly && !uiSourceCode.isDirty())
154 continue;
155 if (searchConfig.filePathMatchesFileQuery(uiSourceCode.fullDisplayName()))
156 result.push(uiSourceCode.url());
157 }
158 result.sort(String.naturalOrderComparator);
159 return result;
160 }
161
162 /**
163 * @param {number} searchId
164 * @param {!Workspace.Project} project
165 * @param {!Workspace.ProjectSearchConfig} searchConfig
166 * @param {!Array<string>} filesMathingFileQuery
167 * @param {!Array<string>} files
168 */
169 _processMatchingFilesForProject(searchId, project, searchConfig, filesMathingFileQuery, files) {
170 if (searchId !== this._searchId) {
171 this._searchFinishedCallback(false);
172 return;
173 }
174
175 files.sort(String.naturalOrderComparator);
176 files = files.intersectOrdered(filesMathingFileQuery, String.naturalOrderComparator);
177 const dirtyFiles = this._projectFilesMatchingFileQuery(project, searchConfig, true);
178 files = files.mergeOrdered(dirtyFiles, String.naturalOrderComparator);
179
180 const uiSourceCodes = [];
181 for (const file of files) {
182 const uiSourceCode = project.uiSourceCodeForURL(file);
183 if (!uiSourceCode)
184 continue;
185 const script = Bindings.DefaultScriptMapping.scriptForUISourceCode(uiSourceCode);
186 if (script && !script.isAnonymousScript())
187 continue;
188 uiSourceCodes.push(uiSourceCode);
189 }
190 uiSourceCodes.sort(Sources.SourcesSearchScope._filesComparator);
191 this._searchResultCandidates =
192 this._searchResultCandidates.mergeOrdered(uiSourceCodes, Sources.SourcesSearchScope._filesComparator);
193 }
194
195 /**
196 * @param {number} searchId
197 * @param {!Common.Progress} progress
198 * @param {function()} callback
199 */
200 _processMatchingFiles(searchId, progress, callback) {
201 if (searchId !== this._searchId) {
202 this._searchFinishedCallback(false);
203 return;
204 }
205
206 const files = this._searchResultCandidates;
207 if (!files.length) {
208 progress.done();
209 callback();
210 return;
211 }
212
213 progress.setTotalWork(files.length);
214
215 let fileIndex = 0;
216 const maxFileContentRequests = 20;
217 let callbacksLeft = 0;
218
219 for (let i = 0; i < maxFileContentRequests && i < files.length; ++i)
220 scheduleSearchInNextFileOrFinish.call(this);
221
222 /**
223 * @param {!Workspace.UISourceCode} uiSourceCode
224 * @this {Sources.SourcesSearchScope}
225 */
226 function searchInNextFile(uiSourceCode) {
227 if (uiSourceCode.isDirty())
228 contentLoaded.call(this, uiSourceCode, uiSourceCode.workingCopy());
229 else
230 uiSourceCode.requestContent().then(contentLoaded.bind(this, uiSourceCode));
231 }
232
233 /**
234 * @this {Sources.SourcesSearchScope}
235 */
236 function scheduleSearchInNextFileOrFinish() {
237 if (fileIndex >= files.length) {
238 if (!callbacksLeft) {
239 progress.done();
240 callback();
241 return;
242 }
243 return;
244 }
245
246 ++callbacksLeft;
247 const uiSourceCode = files[fileIndex++];
248 setTimeout(searchInNextFile.bind(this, uiSourceCode), 0);
249 }
250
251 /**
252 * @param {!Workspace.UISourceCode} uiSourceCode
253 * @param {?string} content
254 * @this {Sources.SourcesSearchScope}
255 */
256 function contentLoaded(uiSourceCode, content) {
257 /**
258 * @param {!Common.ContentProvider.SearchMatch} a
259 * @param {!Common.ContentProvider.SearchMatch} b
260 */
261 function matchesComparator(a, b) {
262 return a.lineNumber - b.lineNumber;
263 }
264
265 progress.worked(1);
266 let matches = [];
267 const queries = this._searchConfig.queries();
268 if (content !== null) {
269 for (let i = 0; i < queries.length; ++i) {
270 const nextMatches = Common.ContentProvider.performSearchInContent(
271 content, queries[i], !this._searchConfig.ignoreCase(), this._searchConfig.isRegex());
272 matches = matches.mergeOrdered(nextMatches, matchesComparator);
273 }
274 }
275 if (matches) {
276 const searchResult = new Sources.FileBasedSearchResult(uiSourceCode, matches);
277 this._searchResultCallback(searchResult);
278 }
279
280 --callbacksLeft;
281 scheduleSearchInNextFileOrFinish.call(this);
282 }
283 }
284
285 /**
286 * @override
287 */
288 stopSearch() {
289 ++this._searchId;
290 }
291};
292
293
294/**
295 * @implements {Search.SearchResult}
296 */
297Sources.FileBasedSearchResult = class {
298 /**
299 * @param {!Workspace.UISourceCode} uiSourceCode
300 * @param {!Array.<!Common.ContentProvider.SearchMatch>} searchMatches
301 */
302 constructor(uiSourceCode, searchMatches) {
303 this._uiSourceCode = uiSourceCode;
304 this._searchMatches = searchMatches;
305 }
306
307 /**
308 * @override
309 * @return {string}
310 */
311 label() {
312 return this._uiSourceCode.displayName();
313 }
314
315 /**
316 * @override
317 * @return {string}
318 */
319 description() {
320 return this._uiSourceCode.fullDisplayName();
321 }
322
323 /**
324 * @override
325 * @return {number}
326 */
327 matchesCount() {
328 return this._searchMatches.length;
329 }
330
331 /**
332 * @override
333 * @param {number} index
334 * @return {string}
335 */
336 matchLineContent(index) {
337 return this._searchMatches[index].lineContent;
338 }
339
340 /**
341 * @override
342 * @param {number} index
343 * @return {!Object}
344 */
345 matchRevealable(index) {
346 const match = this._searchMatches[index];
Tim van der Lippeffa78622019-09-16 12:07:12347 return this._uiSourceCode.uiLocation(match.lineNumber, undefined);
Blink Reformat4c46d092018-04-07 15:32:37348 }
349
350 /**
351 * @override
352 * @param {number} index
353 * @return {?}
354 */
355 matchLabel(index) {
356 return this._searchMatches[index].lineNumber + 1;
357 }
358};