@theia/search-in-workspace 1.45.1 → 1.46.0-next.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +40 -40
  2. package/lib/browser/components/search-in-workspace-input.d.ts +39 -39
  3. package/lib/browser/components/search-in-workspace-input.js +123 -123
  4. package/lib/browser/components/search-in-workspace-textarea.d.ts +39 -39
  5. package/lib/browser/components/search-in-workspace-textarea.js +130 -130
  6. package/lib/browser/search-in-workspace-context-key-service.d.ts +23 -23
  7. package/lib/browser/search-in-workspace-context-key-service.js +90 -90
  8. package/lib/browser/search-in-workspace-factory.d.ts +10 -10
  9. package/lib/browser/search-in-workspace-factory.js +68 -68
  10. package/lib/browser/search-in-workspace-frontend-contribution.d.ts +57 -55
  11. package/lib/browser/search-in-workspace-frontend-contribution.d.ts.map +1 -1
  12. package/lib/browser/search-in-workspace-frontend-contribution.js +516 -482
  13. package/lib/browser/search-in-workspace-frontend-contribution.js.map +1 -1
  14. package/lib/browser/search-in-workspace-frontend-module.d.ts +6 -6
  15. package/lib/browser/search-in-workspace-frontend-module.js +71 -71
  16. package/lib/browser/search-in-workspace-label-provider.d.ts +9 -9
  17. package/lib/browser/search-in-workspace-label-provider.js +57 -57
  18. package/lib/browser/search-in-workspace-preferences.d.ts +17 -17
  19. package/lib/browser/search-in-workspace-preferences.js +87 -87
  20. package/lib/browser/search-in-workspace-result-tree-widget.d.ts +259 -255
  21. package/lib/browser/search-in-workspace-result-tree-widget.d.ts.map +1 -1
  22. package/lib/browser/search-in-workspace-result-tree-widget.js +1172 -1099
  23. package/lib/browser/search-in-workspace-result-tree-widget.js.map +1 -1
  24. package/lib/browser/search-in-workspace-service.d.ts +35 -35
  25. package/lib/browser/search-in-workspace-service.js +158 -158
  26. package/lib/browser/search-in-workspace-widget.d.ts +121 -121
  27. package/lib/browser/search-in-workspace-widget.js +629 -629
  28. package/lib/browser/search-layout-migrations.d.ts +5 -5
  29. package/lib/browser/search-layout-migrations.js +64 -64
  30. package/lib/common/search-in-workspace-interface.d.ts +116 -116
  31. package/lib/common/search-in-workspace-interface.js +35 -35
  32. package/lib/node/ripgrep-search-in-workspace-server.d.ts +94 -94
  33. package/lib/node/ripgrep-search-in-workspace-server.js +430 -430
  34. package/lib/node/ripgrep-search-in-workspace-server.js.map +1 -1
  35. package/lib/node/ripgrep-search-in-workspace-server.slow-spec.d.ts +1 -1
  36. package/lib/node/ripgrep-search-in-workspace-server.slow-spec.js +899 -899
  37. package/lib/node/ripgrep-search-in-workspace-server.slow-spec.js.map +1 -1
  38. package/lib/node/search-in-workspace-backend-module.d.ts +3 -3
  39. package/lib/node/search-in-workspace-backend-module.js +32 -32
  40. package/package.json +9 -9
  41. package/src/browser/components/search-in-workspace-input.tsx +139 -139
  42. package/src/browser/components/search-in-workspace-textarea.tsx +153 -153
  43. package/src/browser/search-in-workspace-context-key-service.ts +93 -93
  44. package/src/browser/search-in-workspace-factory.ts +59 -59
  45. package/src/browser/search-in-workspace-frontend-contribution.ts +510 -474
  46. package/src/browser/search-in-workspace-frontend-module.ts +83 -83
  47. package/src/browser/search-in-workspace-label-provider.ts +48 -48
  48. package/src/browser/search-in-workspace-preferences.ts +96 -96
  49. package/src/browser/search-in-workspace-result-tree-widget.tsx +1318 -1245
  50. package/src/browser/search-in-workspace-service.ts +152 -152
  51. package/src/browser/search-in-workspace-widget.tsx +727 -727
  52. package/src/browser/search-layout-migrations.ts +53 -53
  53. package/src/browser/styles/index.css +400 -400
  54. package/src/browser/styles/search.svg +6 -6
  55. package/src/common/search-in-workspace-interface.ts +153 -153
  56. package/src/node/ripgrep-search-in-workspace-server.slow-spec.ts +1073 -1073
  57. package/src/node/ripgrep-search-in-workspace-server.ts +490 -490
  58. package/src/node/search-in-workspace-backend-module.ts +33 -33
@@ -1,1100 +1,1173 @@
1
- "use strict";
2
- // *****************************************************************************
3
- // Copyright (C) 2018 TypeFox and others.
4
- //
5
- // This program and the accompanying materials are made available under the
6
- // terms of the Eclipse Public License v. 2.0 which is available at
7
- // http://www.eclipse.org/legal/epl-2.0.
8
- //
9
- // This Source Code may also be made available under the following Secondary
10
- // Licenses when the conditions for such availability set forth in the Eclipse
11
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
- // with the GNU Classpath Exception which is available at
13
- // https://www.gnu.org/software/classpath/license.html.
14
- //
15
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
- // *****************************************************************************
17
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
18
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
20
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
21
- return c > 3 && r && Object.defineProperty(target, key, r), r;
22
- };
23
- var __metadata = (this && this.__metadata) || function (k, v) {
24
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
25
- };
26
- var __param = (this && this.__param) || function (paramIndex, decorator) {
27
- return function (target, key) { decorator(target, key, paramIndex); }
28
- };
29
- Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.SearchInWorkspaceResultTreeWidget = exports.SearchInWorkspaceResultLineNode = exports.SearchInWorkspaceFileNode = exports.SearchInWorkspaceRootFolderNode = exports.SearchInWorkspaceRoot = void 0;
31
- const inversify_1 = require("@theia/core/shared/inversify");
32
- const browser_1 = require("@theia/core/lib/browser");
33
- const core_1 = require("@theia/core");
34
- const browser_2 = require("@theia/editor/lib/browser");
35
- const browser_3 = require("@theia/workspace/lib/browser");
36
- const browser_4 = require("@theia/filesystem/lib/browser");
37
- const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
38
- const search_in_workspace_service_1 = require("./search-in-workspace-service");
39
- const common_1 = require("@theia/core/lib/common");
40
- const uri_1 = require("@theia/core/lib/common/uri");
41
- const React = require("@theia/core/shared/react");
42
- const search_in_workspace_preferences_1 = require("./search-in-workspace-preferences");
43
- const color_registry_1 = require("@theia/core/lib/browser/color-registry");
44
- const minimatch = require("minimatch");
45
- const disposable_1 = require("@theia/core/lib/common/disposable");
46
- const debounce = require("@theia/core/shared/lodash.debounce");
47
- const nls_1 = require("@theia/core/lib/common/nls");
48
- const ROOT_ID = 'ResultTree';
49
- var SearchInWorkspaceRoot;
50
- (function (SearchInWorkspaceRoot) {
51
- function is(node) {
52
- return browser_1.CompositeTreeNode.is(node) && node.id === ROOT_ID;
53
- }
54
- SearchInWorkspaceRoot.is = is;
55
- })(SearchInWorkspaceRoot = exports.SearchInWorkspaceRoot || (exports.SearchInWorkspaceRoot = {}));
56
- var SearchInWorkspaceRootFolderNode;
57
- (function (SearchInWorkspaceRootFolderNode) {
58
- function is(node) {
59
- return browser_1.ExpandableTreeNode.is(node) && browser_1.SelectableTreeNode.is(node) && 'path' in node && 'folderUri' in node && !('fileUri' in node);
60
- }
61
- SearchInWorkspaceRootFolderNode.is = is;
62
- })(SearchInWorkspaceRootFolderNode = exports.SearchInWorkspaceRootFolderNode || (exports.SearchInWorkspaceRootFolderNode = {}));
63
- var SearchInWorkspaceFileNode;
64
- (function (SearchInWorkspaceFileNode) {
65
- function is(node) {
66
- return browser_1.ExpandableTreeNode.is(node) && browser_1.SelectableTreeNode.is(node) && 'path' in node && 'fileUri' in node && !('folderUri' in node);
67
- }
68
- SearchInWorkspaceFileNode.is = is;
69
- })(SearchInWorkspaceFileNode = exports.SearchInWorkspaceFileNode || (exports.SearchInWorkspaceFileNode = {}));
70
- var SearchInWorkspaceResultLineNode;
71
- (function (SearchInWorkspaceResultLineNode) {
72
- function is(node) {
73
- return browser_1.SelectableTreeNode.is(node) && 'line' in node && 'character' in node && 'lineText' in node;
74
- }
75
- SearchInWorkspaceResultLineNode.is = is;
76
- })(SearchInWorkspaceResultLineNode = exports.SearchInWorkspaceResultLineNode || (exports.SearchInWorkspaceResultLineNode = {}));
77
- let SearchInWorkspaceResultTreeWidget = class SearchInWorkspaceResultTreeWidget extends browser_1.TreeWidget {
78
- constructor(props, model, contextMenuRenderer) {
79
- super(props, model, contextMenuRenderer);
80
- this._showReplaceButtons = false;
81
- this._replaceTerm = '';
82
- this.searchTerm = '';
83
- this.startSearchOnModification = (activeEditor) => debounce(() => this.searchActiveEditor(activeEditor, this.searchTerm, this.searchOptions), this.searchOnEditorModificationDelay);
84
- this.searchOnEditorModificationDelay = 300;
85
- this.toDisposeOnActiveEditorChanged = new disposable_1.DisposableCollection();
86
- // The default root name to add external search results in the case that a workspace is opened.
87
- this.defaultRootName = nls_1.nls.localizeByDefault('Other files');
88
- this.forceVisibleRootNode = false;
89
- this.appliedDecorations = new Map();
90
- this.changeEmitter = new core_1.Emitter();
91
- this.onExpansionChangedEmitter = new core_1.Emitter();
92
- this.onExpansionChanged = this.onExpansionChangedEmitter.event;
93
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
- this.focusInputEmitter = new core_1.Emitter();
95
- this.remove = (node, e) => this.doRemove(node, e);
96
- model.root = {
97
- id: ROOT_ID,
98
- parent: undefined,
99
- visible: false,
100
- children: []
101
- };
102
- this.toDispose.push(model.onSelectionChanged(nodes => {
103
- const node = nodes[0];
104
- if (SearchInWorkspaceResultLineNode.is(node)) {
105
- this.doOpen(node, true, true);
106
- }
107
- }));
108
- this.toDispose.push(model.onOpenNode(node => {
109
- if (SearchInWorkspaceResultLineNode.is(node)) {
110
- this.doOpen(node, true, false);
111
- }
112
- }));
113
- this.resultTree = new Map();
114
- this.toDispose.push(model.onNodeRefreshed(() => this.changeEmitter.fire(this.resultTree)));
115
- }
116
- init() {
117
- super.init();
118
- this.addClass('resultContainer');
119
- this.toDispose.push(this.changeEmitter);
120
- this.toDispose.push(this.focusInputEmitter);
121
- this.toDispose.push(this.editorManager.onActiveEditorChanged(activeEditor => {
122
- this.updateCurrentEditorDecorations();
123
- this.toDisposeOnActiveEditorChanged.dispose();
124
- this.toDispose.push(this.toDisposeOnActiveEditorChanged);
125
- if (activeEditor) {
126
- this.toDisposeOnActiveEditorChanged.push(activeEditor.editor.onDocumentContentChanged(() => {
127
- if (this.searchTerm !== '' && this.searchInWorkspacePreferences['search.searchOnEditorModification']) {
128
- this.startSearchOnModification(activeEditor)();
129
- }
130
- }));
131
- }
132
- }));
133
- this.toDispose.push(this.searchInWorkspacePreferences.onPreferenceChanged(() => {
134
- this.update();
135
- }));
136
- this.toDispose.push(this.fileService.onDidFilesChange(event => {
137
- if (event.gotDeleted()) {
138
- event.getDeleted().forEach(deletedFile => {
139
- const fileNodes = this.getFileNodesByUri(deletedFile.resource);
140
- fileNodes.forEach(node => this.removeFileNode(node));
141
- });
142
- this.model.refresh();
143
- }
144
- }));
145
- this.toDispose.push(this.model.onExpansionChanged(() => {
146
- this.onExpansionChangedEmitter.fire(undefined);
147
- }));
148
- }
149
- get fileNumber() {
150
- let num = 0;
151
- for (const rootFolderNode of this.resultTree.values()) {
152
- num += rootFolderNode.children.length;
153
- }
154
- return num;
155
- }
156
- set showReplaceButtons(srb) {
157
- this._showReplaceButtons = srb;
158
- this.update();
159
- }
160
- set replaceTerm(rt) {
161
- this._replaceTerm = rt;
162
- this.update();
163
- }
164
- get isReplacing() {
165
- return this._replaceTerm !== '' && this._showReplaceButtons;
166
- }
167
- get onChange() {
168
- return this.changeEmitter.event;
169
- }
170
- get onFocusInput() {
171
- return this.focusInputEmitter.event;
172
- }
173
- collapseAll() {
174
- for (const rootFolderNode of this.resultTree.values()) {
175
- for (const fileNode of rootFolderNode.children) {
176
- this.expansionService.collapseNode(fileNode);
177
- }
178
- if (rootFolderNode.visible) {
179
- this.expansionService.collapseNode(rootFolderNode);
180
- }
181
- }
182
- }
183
- expandAll() {
184
- for (const rootFolderNode of this.resultTree.values()) {
185
- for (const fileNode of rootFolderNode.children) {
186
- this.expansionService.expandNode(fileNode);
187
- }
188
- if (rootFolderNode.visible) {
189
- this.expansionService.expandNode(rootFolderNode);
190
- }
191
- }
192
- }
193
- areResultsCollapsed() {
194
- for (const rootFolderNode of this.resultTree.values()) {
195
- for (const fileNode of rootFolderNode.children) {
196
- if (!browser_1.ExpandableTreeNode.isCollapsed(fileNode)) {
197
- return false;
198
- }
199
- }
200
- }
201
- return true;
202
- }
203
- /**
204
- * Find matches for the given editor.
205
- * @param searchTerm the search term.
206
- * @param widget the editor widget.
207
- * @param searchOptions the search options to apply.
208
- *
209
- * @returns the list of matches.
210
- */
211
- findMatches(searchTerm, widget, searchOptions) {
212
- if (!widget.editor.document.findMatches) {
213
- return [];
214
- }
215
- const results = widget.editor.document.findMatches({
216
- searchString: searchTerm,
217
- isRegex: !!searchOptions.useRegExp,
218
- matchCase: !!searchOptions.matchCase,
219
- matchWholeWord: !!searchOptions.matchWholeWord,
220
- limitResultCount: searchOptions.maxResults
221
- });
222
- const matches = [];
223
- results.forEach(r => {
224
- const numberOfLines = searchTerm.split('\n').length;
225
- const lineTexts = [];
226
- for (let i = 0; i < numberOfLines; i++) {
227
- lineTexts.push(widget.editor.document.getLineContent(r.range.start.line + i));
228
- }
229
- matches.push({
230
- line: r.range.start.line,
231
- character: r.range.start.character,
232
- length: searchTerm.length,
233
- lineText: lineTexts.join('\n')
234
- });
235
- });
236
- return matches;
237
- }
238
- /**
239
- * Convert a pattern to match all directories.
240
- * @param workspaceRootUri the uri of the current workspace root.
241
- * @param pattern the pattern to be converted.
242
- */
243
- convertPatternToGlob(workspaceRootUri, pattern) {
244
- if (pattern.startsWith('**/')) {
245
- return pattern;
246
- }
247
- if (pattern.startsWith('./')) {
248
- if (workspaceRootUri === undefined) {
249
- return pattern;
250
- }
251
- return workspaceRootUri.toString() + pattern.replace('./', '/');
252
- }
253
- return pattern.startsWith('/')
254
- ? '**' + pattern
255
- : '**/' + pattern;
256
- }
257
- /**
258
- * Determine if the URI matches any of the patterns.
259
- * @param uri the editor URI.
260
- * @param patterns the glob patterns to verify.
261
- */
262
- inPatternList(uri, patterns) {
263
- const opts = { dot: true, matchBase: true };
264
- return patterns.some(pattern => minimatch(uri.toString(), this.convertPatternToGlob(this.workspaceService.getWorkspaceRootUri(uri), pattern), opts));
265
- }
266
- /**
267
- * Determine if the given editor satisfies the filtering criteria.
268
- * An editor should be searched only if:
269
- * - it is not excluded through the `excludes` list.
270
- * - it is not explicitly present in a non-empty `includes` list.
271
- */
272
- shouldApplySearch(editorWidget, searchOptions) {
273
- const excludePatterns = this.getExcludeGlobs(searchOptions.exclude);
274
- if (this.inPatternList(editorWidget.editor.uri, excludePatterns)) {
275
- return false;
276
- }
277
- const includePatterns = searchOptions.include;
278
- if (!!(includePatterns === null || includePatterns === void 0 ? void 0 : includePatterns.length) && !this.inPatternList(editorWidget.editor.uri, includePatterns)) {
279
- return false;
280
- }
281
- return true;
282
- }
283
- /**
284
- * Search the active editor only and update the tree with those results.
285
- */
286
- searchActiveEditor(activeEditor, searchTerm, searchOptions) {
287
- const includesExternalResults = () => !!this.resultTree.get(this.defaultRootName);
288
- // Check if outside workspace results are present before searching.
289
- const hasExternalResultsBefore = includesExternalResults();
290
- // Collect search results for the given editor.
291
- const results = this.searchInEditor(activeEditor, searchTerm, searchOptions);
292
- // Update the tree by removing the result node, and add new results if applicable.
293
- this.getFileNodesByUri(activeEditor.editor.uri).forEach(fileNode => this.removeFileNode(fileNode));
294
- if (results) {
295
- this.appendToResultTree(results);
296
- }
297
- // Check if outside workspace results are present after searching.
298
- const hasExternalResultsAfter = includesExternalResults();
299
- // Redo a search to update the tree node visibility if:
300
- // + `Other files` node was present, now it is not.
301
- // + `Other files` node was not present, now it is.
302
- if (hasExternalResultsBefore ? !hasExternalResultsAfter : hasExternalResultsAfter) {
303
- this.search(this.searchTerm, this.searchOptions);
304
- return;
305
- }
306
- this.handleSearchCompleted();
307
- }
308
- /**
309
- * Perform a search in all open editors.
310
- * @param searchTerm the search term.
311
- * @param searchOptions the search options to apply.
312
- *
313
- * @returns the tuple of result count, and the list of search results.
314
- */
315
- searchInOpenEditors(searchTerm, searchOptions) {
316
- // Track the number of results found.
317
- let numberOfResults = 0;
318
- const searchResults = [];
319
- this.editorManager.all.forEach(e => {
320
- const editorResults = this.searchInEditor(e, searchTerm, searchOptions);
321
- if (editorResults) {
322
- numberOfResults += editorResults.matches.length;
323
- searchResults.push(editorResults);
324
- }
325
- });
326
- return {
327
- numberOfResults,
328
- matches: searchResults
329
- };
330
- }
331
- /**
332
- * Perform a search in the target editor.
333
- * @param editorWidget the editor widget.
334
- * @param searchTerm the search term.
335
- * @param searchOptions the search options to apply.
336
- *
337
- * @returns the search results from the given editor, undefined if the editor is either filtered or has no matches found.
338
- */
339
- searchInEditor(editorWidget, searchTerm, searchOptions) {
340
- var _a;
341
- if (!this.shouldApplySearch(editorWidget, searchOptions)) {
342
- return undefined;
343
- }
344
- const matches = this.findMatches(searchTerm, editorWidget, searchOptions);
345
- if (matches.length <= 0) {
346
- return undefined;
347
- }
348
- const fileUri = editorWidget.editor.uri.toString();
349
- const root = (_a = this.workspaceService.getWorkspaceRootUri(editorWidget.editor.uri)) === null || _a === void 0 ? void 0 : _a.toString();
350
- return {
351
- root: root !== null && root !== void 0 ? root : this.defaultRootName,
352
- fileUri,
353
- matches
354
- };
355
- }
356
- /**
357
- * Append search results to the result tree.
358
- * @param result Search result.
359
- */
360
- appendToResultTree(result) {
361
- const collapseValue = this.searchInWorkspacePreferences['search.collapseResults'];
362
- let path;
363
- if (result.root === this.defaultRootName) {
364
- path = new uri_1.default(result.fileUri).path.dir.fsPath();
365
- }
366
- else {
367
- path = this.filenameAndPath(result.root, result.fileUri).path;
368
- }
369
- const tree = this.resultTree;
370
- let rootFolderNode = tree.get(result.root);
371
- if (!rootFolderNode) {
372
- rootFolderNode = this.createRootFolderNode(result.root);
373
- tree.set(result.root, rootFolderNode);
374
- }
375
- let fileNode = rootFolderNode.children.find(f => f.fileUri === result.fileUri);
376
- if (!fileNode) {
377
- fileNode = this.createFileNode(result.root, path, result.fileUri, rootFolderNode);
378
- rootFolderNode.children.push(fileNode);
379
- }
380
- for (const match of result.matches) {
381
- const line = this.createResultLineNode(result, match, fileNode);
382
- if (fileNode.children.findIndex(lineNode => lineNode.id === line.id) < 0) {
383
- fileNode.children.push(line);
384
- }
385
- }
386
- this.collapseFileNode(fileNode, collapseValue);
387
- }
388
- /**
389
- * Handle when searching completed.
390
- */
391
- handleSearchCompleted(cancelIndicator) {
392
- if (cancelIndicator) {
393
- cancelIndicator.cancel();
394
- }
395
- this.sortResultTree();
396
- this.refreshModelChildren();
397
- }
398
- /**
399
- * Sort the result tree by URIs.
400
- */
401
- sortResultTree() {
402
- // Sort the result map by folder URI.
403
- const entries = [...this.resultTree.entries()];
404
- entries.sort(([, a], [, b]) => this.compare(a.folderUri, b.folderUri));
405
- this.resultTree = new Map(entries);
406
- // Update the list of children nodes, sorting them by their file URI.
407
- entries.forEach(([, folder]) => {
408
- folder.children.sort((a, b) => this.compare(a.fileUri, b.fileUri));
409
- });
410
- }
411
- /**
412
- * Search and populate the result tree with matches.
413
- * @param searchTerm the search term.
414
- * @param searchOptions the search options to apply.
415
- */
416
- async search(searchTerm, searchOptions) {
417
- this.searchTerm = searchTerm;
418
- this.searchOptions = searchOptions;
419
- searchOptions = {
420
- ...searchOptions,
421
- exclude: this.getExcludeGlobs(searchOptions.exclude)
422
- };
423
- this.resultTree.clear();
424
- this.forceVisibleRootNode = false;
425
- if (this.cancelIndicator) {
426
- this.cancelIndicator.cancel();
427
- }
428
- if (searchTerm === '') {
429
- this.refreshModelChildren();
430
- return;
431
- }
432
- this.cancelIndicator = new core_1.CancellationTokenSource();
433
- const cancelIndicator = this.cancelIndicator;
434
- const token = this.cancelIndicator.token;
435
- const progress = await this.progressService.showProgress({ text: `search: ${searchTerm}`, options: { location: 'search' } });
436
- token.onCancellationRequested(() => {
437
- progress.cancel();
438
- if (searchId) {
439
- this.searchService.cancel(searchId);
440
- }
441
- this.cancelIndicator = undefined;
442
- this.changeEmitter.fire(this.resultTree);
443
- });
444
- // Collect search results for opened editors which otherwise may not be found by ripgrep (ex: dirty editors).
445
- const { numberOfResults, matches } = this.searchInOpenEditors(searchTerm, searchOptions);
446
- // The root node is visible if outside workspace results are found and workspace root(s) are present.
447
- this.forceVisibleRootNode = matches.some(m => m.root === this.defaultRootName) && this.workspaceService.opened;
448
- matches.forEach(m => this.appendToResultTree(m));
449
- // Exclude files already covered by searching open editors.
450
- this.editorManager.all.forEach(e => {
451
- const excludePath = e.editor.uri.path.toString();
452
- searchOptions.exclude = searchOptions.exclude ? searchOptions.exclude.concat(excludePath) : [excludePath];
453
- });
454
- // Reduce `maxResults` due to editor results.
455
- if (searchOptions.maxResults) {
456
- searchOptions.maxResults -= numberOfResults;
457
- }
458
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
459
- let pendingRefreshTimeout;
460
- const searchId = await this.searchService.search(searchTerm, {
461
- onResult: (aSearchId, result) => {
462
- if (token.isCancellationRequested || aSearchId !== searchId) {
463
- return;
464
- }
465
- this.appendToResultTree(result);
466
- if (pendingRefreshTimeout) {
467
- clearTimeout(pendingRefreshTimeout);
468
- }
469
- pendingRefreshTimeout = setTimeout(() => this.refreshModelChildren(), 100);
470
- },
471
- onDone: () => {
472
- this.handleSearchCompleted(cancelIndicator);
473
- }
474
- }, searchOptions).catch(() => {
475
- this.handleSearchCompleted(cancelIndicator);
476
- });
477
- }
478
- focusFirstResult() {
479
- if (SearchInWorkspaceRoot.is(this.model.root) && this.model.root.children.length > 0) {
480
- const node = this.model.root.children[0];
481
- if (browser_1.SelectableTreeNode.is(node)) {
482
- this.node.focus();
483
- this.model.selectNode(node);
484
- }
485
- }
486
- }
487
- /**
488
- * Collapse the search-in-workspace file node
489
- * based on the preference value.
490
- */
491
- collapseFileNode(node, preferenceValue) {
492
- if (preferenceValue === 'auto' && node.children.length >= 10) {
493
- node.expanded = false;
494
- }
495
- else if (preferenceValue === 'alwaysCollapse') {
496
- node.expanded = false;
497
- }
498
- else if (preferenceValue === 'alwaysExpand') {
499
- node.expanded = true;
500
- }
501
- }
502
- handleUp(event) {
503
- if (!this.model.getPrevSelectableNode(this.model.getFocusedNode())) {
504
- this.focusInputEmitter.fire(true);
505
- }
506
- else {
507
- super.handleUp(event);
508
- }
509
- }
510
- async refreshModelChildren() {
511
- if (SearchInWorkspaceRoot.is(this.model.root)) {
512
- this.model.root.children = Array.from(this.resultTree.values());
513
- this.model.refresh();
514
- this.updateCurrentEditorDecorations();
515
- }
516
- }
517
- updateCurrentEditorDecorations() {
518
- this.shell.allTabBars.forEach(tb => {
519
- const currentTitle = tb.currentTitle;
520
- if (currentTitle && currentTitle.owner instanceof browser_2.EditorWidget) {
521
- const widget = currentTitle.owner;
522
- const fileNodes = this.getFileNodesByUri(widget.editor.uri);
523
- if (fileNodes.length > 0) {
524
- fileNodes.forEach(node => {
525
- this.decorateEditor(node, widget);
526
- });
527
- }
528
- else {
529
- this.decorateEditor(undefined, widget);
530
- }
531
- }
532
- });
533
- const currentWidget = this.editorManager.currentEditor;
534
- if (currentWidget) {
535
- const fileNodes = this.getFileNodesByUri(currentWidget.editor.uri);
536
- fileNodes.forEach(node => {
537
- this.decorateEditor(node, currentWidget);
538
- });
539
- }
540
- }
541
- createRootFolderNode(rootUri) {
542
- const uri = new uri_1.default(rootUri);
543
- return {
544
- selected: false,
545
- path: uri.path.fsPath(),
546
- folderUri: rootUri,
547
- uri: new uri_1.default(rootUri),
548
- children: [],
549
- expanded: true,
550
- id: rootUri,
551
- parent: this.model.root,
552
- visible: this.forceVisibleRootNode || this.workspaceService.isMultiRootWorkspaceOpened
553
- };
554
- }
555
- createFileNode(rootUri, path, fileUri, parent) {
556
- return {
557
- selected: false,
558
- path,
559
- children: [],
560
- expanded: true,
561
- id: `${rootUri}::${fileUri}`,
562
- parent,
563
- fileUri,
564
- uri: new uri_1.default(fileUri),
565
- };
566
- }
567
- createResultLineNode(result, match, fileNode) {
568
- return {
569
- ...result,
570
- ...match,
571
- selected: false,
572
- id: result.fileUri + '-' + match.line + '-' + match.character + '-' + match.length,
573
- name: typeof match.lineText === 'string' ? match.lineText : match.lineText.text,
574
- parent: fileNode
575
- };
576
- }
577
- getFileNodesByUri(uri) {
578
- const nodes = [];
579
- const fileUri = uri.withScheme('file').toString();
580
- for (const rootFolderNode of this.resultTree.values()) {
581
- const rootUri = new uri_1.default(rootFolderNode.path).withScheme('file');
582
- if (rootUri.isEqualOrParent(uri) || rootFolderNode.id === this.defaultRootName) {
583
- for (const fileNode of rootFolderNode.children) {
584
- if (fileNode.fileUri === fileUri) {
585
- nodes.push(fileNode);
586
- }
587
- }
588
- }
589
- }
590
- return nodes;
591
- }
592
- filenameAndPath(rootUriStr, uriStr) {
593
- const uri = new uri_1.default(uriStr);
594
- const relativePath = new uri_1.default(rootUriStr).relative(uri.parent);
595
- return {
596
- name: this.labelProvider.getName(uri),
597
- path: relativePath ? relativePath.fsPath() : ''
598
- };
599
- }
600
- getDepthPadding(depth) {
601
- return super.getDepthPadding(depth) + 5;
602
- }
603
- renderCaption(node, props) {
604
- if (SearchInWorkspaceRootFolderNode.is(node)) {
605
- return this.renderRootFolderNode(node);
606
- }
607
- else if (SearchInWorkspaceFileNode.is(node)) {
608
- return this.renderFileNode(node);
609
- }
610
- else if (SearchInWorkspaceResultLineNode.is(node)) {
611
- return this.renderResultLineNode(node);
612
- }
613
- return '';
614
- }
615
- renderTailDecorations(node, props) {
616
- return React.createElement("div", { className: 'result-node-buttons' },
617
- this._showReplaceButtons && this.renderReplaceButton(node),
618
- this.renderRemoveButton(node));
619
- }
620
- doReplace(node, e) {
621
- const selection = browser_1.SelectableTreeNode.isSelected(node) ? this.selectionService.selection : [node];
622
- selection.forEach(n => this.replace(n));
623
- e.stopPropagation();
624
- }
625
- renderReplaceButton(node) {
626
- const isResultLineNode = SearchInWorkspaceResultLineNode.is(node);
627
- return React.createElement("span", { className: isResultLineNode ? (0, browser_1.codicon)('replace') : (0, browser_1.codicon)('replace-all'), onClick: e => this.doReplace(node, e), title: isResultLineNode
628
- ? nls_1.nls.localizeByDefault('Replace')
629
- : nls_1.nls.localizeByDefault('Replace All') });
630
- }
631
- getFileCount(node) {
632
- if (SearchInWorkspaceRoot.is(node)) {
633
- return node.children.reduce((acc, current) => acc + this.getFileCount(current), 0);
634
- }
635
- else if (SearchInWorkspaceRootFolderNode.is(node)) {
636
- return node.children.length;
637
- }
638
- else if (SearchInWorkspaceFileNode.is(node)) {
639
- return 1;
640
- }
641
- return 0;
642
- }
643
- getResultCount(node) {
644
- if (SearchInWorkspaceRoot.is(node)) {
645
- return node.children.reduce((acc, current) => acc + this.getResultCount(current), 0);
646
- }
647
- else if (SearchInWorkspaceRootFolderNode.is(node)) {
648
- return node.children.reduce((acc, current) => acc + this.getResultCount(current), 0);
649
- }
650
- else if (SearchInWorkspaceFileNode.is(node)) {
651
- return node.children.length;
652
- }
653
- else if (SearchInWorkspaceResultLineNode.is(node)) {
654
- return 1;
655
- }
656
- return 0;
657
- }
658
- /**
659
- * Replace results under the node passed into the function. If node is undefined, replace all results.
660
- * @param node Node in the tree widget where the "replace all" operation is performed
661
- */
662
- async replace(node) {
663
- const replaceForNode = node || this.model.root;
664
- const needConfirm = !SearchInWorkspaceFileNode.is(node) && !SearchInWorkspaceResultLineNode.is(node);
665
- const replacementText = this._replaceTerm;
666
- if (!needConfirm || await this.confirmReplaceAll(this.getResultCount(replaceForNode), this.getFileCount(replaceForNode), replacementText)) {
667
- (node ? [node] : Array.from(this.resultTree.values())).forEach(n => {
668
- this.replaceResult(n, !!node, replacementText);
669
- this.removeNode(n);
670
- });
671
- }
672
- }
673
- confirmReplaceAll(resultNumber, fileNumber, replacementText) {
674
- return new browser_1.ConfirmDialog({
675
- title: nls_1.nls.localizeByDefault('Replace All'),
676
- msg: this.buildReplaceAllConfirmationMessage(resultNumber, fileNumber, replacementText)
677
- }).open();
678
- }
679
- buildReplaceAllConfirmationMessage(occurrences, fileCount, replaceValue) {
680
- if (occurrences === 1) {
681
- if (fileCount === 1) {
682
- if (replaceValue) {
683
- return nls_1.nls.localizeByDefault("Replace {0} occurrence across {1} file with '{2}'?", occurrences, fileCount, replaceValue);
684
- }
685
- return nls_1.nls.localizeByDefault('Replace {0} occurrence across {1} file?', occurrences, fileCount);
686
- }
687
- if (replaceValue) {
688
- return nls_1.nls.localizeByDefault("Replace {0} occurrence across {1} files with '{2}'?", occurrences, fileCount, replaceValue);
689
- }
690
- return nls_1.nls.localizeByDefault('Replace {0} occurrence across {1} files?', occurrences, fileCount);
691
- }
692
- if (fileCount === 1) {
693
- if (replaceValue) {
694
- return nls_1.nls.localizeByDefault("Replace {0} occurrences across {1} file with '{2}'?", occurrences, fileCount, replaceValue);
695
- }
696
- return nls_1.nls.localizeByDefault('Replace {0} occurrences across {1} file?', occurrences, fileCount);
697
- }
698
- if (replaceValue) {
699
- return nls_1.nls.localizeByDefault("Replace {0} occurrences across {1} files with '{2}'?", occurrences, fileCount, replaceValue);
700
- }
701
- return nls_1.nls.localizeByDefault('Replace {0} occurrences across {1} files?', occurrences, fileCount);
702
- }
703
- updateRightResults(node) {
704
- const fileNode = node.parent;
705
- const rightPositionedNodes = fileNode.children.filter(rl => rl.line === node.line && rl.character > node.character);
706
- const diff = this._replaceTerm.length - this.searchTerm.length;
707
- rightPositionedNodes.forEach(r => r.character += diff);
708
- }
709
- /**
710
- * Replace text either in all search matches under a node or in all search matches, and save the changes.
711
- * @param node - node in the tree widget in which the "replace all" is performed.
712
- * @param {boolean} replaceOne - whether the function is to replace all matches under a node. If it is false, replace all.
713
- * @param replacementText - text to be used for all replacements in the current replacement cycle.
714
- */
715
- async replaceResult(node, replaceOne, replacementText) {
716
- const toReplace = [];
717
- if (SearchInWorkspaceRootFolderNode.is(node)) {
718
- node.children.forEach(fileNode => this.replaceResult(fileNode, replaceOne, replacementText));
719
- }
720
- else if (SearchInWorkspaceFileNode.is(node)) {
721
- toReplace.push(...node.children);
722
- }
723
- else if (SearchInWorkspaceResultLineNode.is(node)) {
724
- toReplace.push(node);
725
- this.updateRightResults(node);
726
- }
727
- if (toReplace.length > 0) {
728
- // Store the state of all tracked editors before another editor widget might be created for text replacing.
729
- const trackedEditors = this.editorManager.all;
730
- // Open the file only if the function is called to replace all matches under a specific node.
731
- const widget = replaceOne ? await this.doOpen(toReplace[0]) : await this.doGetWidget(toReplace[0]);
732
- const source = widget.editor.document.getText();
733
- const replaceOperations = toReplace.map(resultLineNode => ({
734
- text: replacementText,
735
- range: {
736
- start: {
737
- line: resultLineNode.line - 1,
738
- character: resultLineNode.character - 1
739
- },
740
- end: this.findEndCharacterPosition(resultLineNode),
741
- }
742
- }));
743
- // Replace the text.
744
- await widget.editor.replaceText({
745
- source,
746
- replaceOperations
747
- });
748
- // Save the text replacement changes in the editor.
749
- await widget.saveable.save();
750
- // Dispose the widget if it is not opened but created for `replaceAll`.
751
- if (!replaceOne) {
752
- if (trackedEditors.indexOf(widget) === -1) {
753
- widget.dispose();
754
- }
755
- }
756
- }
757
- }
758
- doRemove(node, e) {
759
- const selection = browser_1.SelectableTreeNode.isSelected(node) ? this.selectionService.selection : [node];
760
- selection.forEach(n => this.removeNode(n));
761
- e.stopPropagation();
762
- }
763
- renderRemoveButton(node) {
764
- return React.createElement("span", { className: (0, browser_1.codicon)('close'), onClick: e => this.remove(node, e), title: 'Dismiss' });
765
- }
766
- removeNode(node) {
767
- if (SearchInWorkspaceRootFolderNode.is(node)) {
768
- this.removeRootFolderNode(node);
769
- }
770
- else if (SearchInWorkspaceFileNode.is(node)) {
771
- this.removeFileNode(node);
772
- }
773
- else if (SearchInWorkspaceResultLineNode.is(node)) {
774
- this.removeResultLineNode(node);
775
- }
776
- this.refreshModelChildren();
777
- }
778
- removeRootFolderNode(node) {
779
- for (const rootUri of this.resultTree.keys()) {
780
- if (rootUri === node.folderUri) {
781
- this.resultTree.delete(rootUri);
782
- break;
783
- }
784
- }
785
- }
786
- removeFileNode(node) {
787
- const rootFolderNode = node.parent;
788
- const index = rootFolderNode.children.findIndex(fileNode => fileNode.id === node.id);
789
- if (index > -1) {
790
- rootFolderNode.children.splice(index, 1);
791
- }
792
- if (this.getFileCount(rootFolderNode) === 0) {
793
- this.removeRootFolderNode(rootFolderNode);
794
- }
795
- }
796
- removeResultLineNode(node) {
797
- const fileNode = node.parent;
798
- const index = fileNode.children.findIndex(n => n.fileUri === node.fileUri && n.line === node.line && n.character === node.character);
799
- if (index > -1) {
800
- fileNode.children.splice(index, 1);
801
- if (this.getResultCount(fileNode) === 0) {
802
- this.removeFileNode(fileNode);
803
- }
804
- }
805
- }
806
- findEndCharacterPosition(node) {
807
- const lineText = typeof node.lineText === 'string' ? node.lineText : node.lineText.text;
808
- const lines = lineText.split('\n');
809
- const line = node.line + lines.length - 2;
810
- let character = node.character - 1 + node.length;
811
- if (lines.length > 1) {
812
- character = node.length - lines[0].length + node.character - lines.length;
813
- if (lines.length > 2) {
814
- for (const lineNum of Array(lines.length - 2).keys()) {
815
- character -= lines[lineNum + 1].length;
816
- }
817
- }
818
- }
819
- return { line, character };
820
- }
821
- renderRootFolderNode(node) {
822
- return React.createElement("div", { className: 'result' },
823
- React.createElement("div", { className: 'result-head' },
824
- React.createElement("div", { className: `result-head-info noWrapInfo noselect ${node.selected ? 'selected' : ''}` },
825
- React.createElement("span", { className: `file-icon ${this.toNodeIcon(node) || ''}` }),
826
- React.createElement("div", { className: 'noWrapInfo' },
827
- React.createElement("span", { className: 'file-name' }, this.toNodeName(node)),
828
- node.path !== '/' + this.defaultRootName &&
829
- React.createElement("span", { className: 'file-path ' + browser_1.TREE_NODE_INFO_CLASS }, node.path))),
830
- React.createElement("span", { className: 'notification-count-container highlighted-count-container' },
831
- React.createElement("span", { className: 'notification-count' }, this.getFileCount(node)))));
832
- }
833
- renderFileNode(node) {
834
- return React.createElement("div", { className: 'result' },
835
- React.createElement("div", { className: 'result-head' },
836
- React.createElement("div", { className: `result-head-info noWrapInfo noselect ${node.selected ? 'selected' : ''}`, title: new uri_1.default(node.fileUri).path.fsPath() },
837
- React.createElement("span", { className: `file-icon ${this.toNodeIcon(node)}` }),
838
- React.createElement("div", { className: 'noWrapInfo' },
839
- React.createElement("span", { className: 'file-name' }, this.toNodeName(node)),
840
- React.createElement("span", { className: 'file-path ' + browser_1.TREE_NODE_INFO_CLASS }, node.path))),
841
- React.createElement("span", { className: 'notification-count-container' },
842
- React.createElement("span", { className: 'notification-count' }, this.getResultCount(node)))));
843
- }
844
- renderResultLineNode(node) {
845
- const character = typeof node.lineText === 'string' ? node.character : node.lineText.character;
846
- const lineText = typeof node.lineText === 'string' ? node.lineText : node.lineText.text;
847
- let start = Math.max(0, character - 26);
848
- const wordBreak = /\b/g;
849
- while (start > 0 && wordBreak.test(lineText) && wordBreak.lastIndex < character) {
850
- if (character - wordBreak.lastIndex < 26) {
851
- break;
852
- }
853
- start = wordBreak.lastIndex;
854
- wordBreak.lastIndex++;
855
- }
856
- const before = lineText.slice(start, character - 1).trimStart();
857
- const lineCount = lineText.split('\n').length;
858
- return React.createElement(React.Fragment, null,
859
- React.createElement("div", { className: `resultLine noWrapInfo noselect ${node.selected ? 'selected' : ''}`, title: lineText.trim() },
860
- this.searchInWorkspacePreferences['search.lineNumbers'] && React.createElement("span", { className: 'theia-siw-lineNumber' }, node.line),
861
- React.createElement("span", null, before),
862
- this.renderMatchLinePart(node),
863
- lineCount > 1 || React.createElement("span", null, lineText.slice(node.character + node.length - 1, 250 - before.length + node.length))),
864
- lineCount > 1 && React.createElement("div", { className: 'match-line-num' },
865
- "+",
866
- lineCount - 1));
867
- }
868
- renderMatchLinePart(node) {
869
- const replaceTermLines = this._replaceTerm.split('\n');
870
- const replaceTerm = this.isReplacing ? React.createElement("span", { className: 'replace-term' }, replaceTermLines[0]) : '';
871
- const className = `match${this.isReplacing ? ' strike-through' : ''}`;
872
- const text = typeof node.lineText === 'string' ? node.lineText : node.lineText.text;
873
- const match = text.substring(node.character - 1, node.character + node.length - 1);
874
- const matchLines = match.split('\n');
875
- return React.createElement(React.Fragment, null,
876
- React.createElement("span", { className: className }, matchLines[0]),
877
- replaceTerm);
878
- }
879
- /**
880
- * Get the editor widget by the node.
881
- * @param {SearchInWorkspaceResultLineNode} node - the node representing a match in the search results.
882
- * @returns The editor widget to which the text replace will be done.
883
- */
884
- async doGetWidget(node) {
885
- const fileUri = new uri_1.default(node.fileUri);
886
- const editorWidget = await this.editorManager.getOrCreateByUri(fileUri);
887
- return editorWidget;
888
- }
889
- async doOpen(node, asDiffWidget = false, preview = false) {
890
- let fileUri;
891
- const resultNode = node.parent;
892
- if (resultNode && this.isReplacing && asDiffWidget) {
893
- const leftUri = new uri_1.default(node.fileUri);
894
- const rightUri = await this.createReplacePreview(resultNode);
895
- fileUri = browser_1.DiffUris.encode(leftUri, rightUri);
896
- }
897
- else {
898
- fileUri = new uri_1.default(node.fileUri);
899
- }
900
- const opts = {
901
- selection: {
902
- start: {
903
- line: node.line - 1,
904
- character: node.character - 1
905
- },
906
- end: this.findEndCharacterPosition(node),
907
- },
908
- mode: preview ? 'reveal' : 'activate',
909
- preview,
910
- };
911
- const editorWidget = await this.editorManager.open(fileUri, opts);
912
- if (!browser_1.DiffUris.isDiffUri(fileUri)) {
913
- this.decorateEditor(resultNode, editorWidget);
914
- }
915
- return editorWidget;
916
- }
917
- async createReplacePreview(node) {
918
- const fileUri = new uri_1.default(node.fileUri).withScheme('file');
919
- const openedEditor = this.editorManager.all.find(({ editor }) => editor.uri.toString() === fileUri.toString());
920
- let content;
921
- if (openedEditor) {
922
- content = openedEditor.editor.document.getText();
923
- }
924
- else {
925
- const resource = await this.fileResourceResolver.resolve(fileUri);
926
- content = await resource.readContents();
927
- }
928
- const searchTermRegExp = new RegExp(this.searchTerm, 'g');
929
- return fileUri.withScheme(common_1.MEMORY_TEXT).withQuery(content.replace(searchTermRegExp, this._replaceTerm));
930
- }
931
- decorateEditor(node, editorWidget) {
932
- if (!browser_1.DiffUris.isDiffUri(editorWidget.editor.uri)) {
933
- const key = `${editorWidget.editor.uri.toString()}#search-in-workspace-matches`;
934
- const oldDecorations = this.appliedDecorations.get(key) || [];
935
- const newDecorations = this.createEditorDecorations(node);
936
- const appliedDecorations = editorWidget.editor.deltaDecorations({
937
- newDecorations,
938
- oldDecorations,
939
- });
940
- this.appliedDecorations.set(key, appliedDecorations);
941
- }
942
- }
943
- createEditorDecorations(resultNode) {
944
- const decorations = [];
945
- if (resultNode) {
946
- resultNode.children.forEach(res => {
947
- decorations.push({
948
- range: {
949
- start: {
950
- line: res.line - 1,
951
- character: res.character - 1
952
- },
953
- end: {
954
- line: res.line - 1,
955
- character: res.character - 1 + res.length
956
- }
957
- },
958
- options: {
959
- overviewRuler: {
960
- color: {
961
- id: 'editor.findMatchHighlightBackground'
962
- },
963
- position: browser_2.OverviewRulerLane.Center
964
- },
965
- className: res.selected ? 'current-search-in-workspace-editor-match' : 'search-in-workspace-editor-match',
966
- stickiness: browser_2.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore
967
- }
968
- });
969
- });
970
- }
971
- return decorations;
972
- }
973
- /**
974
- * Get the list of exclude globs.
975
- * @param excludeOptions the exclude search option.
976
- *
977
- * @returns the list of exclude globs.
978
- */
979
- getExcludeGlobs(excludeOptions) {
980
- const excludePreferences = this.filesystemPreferences['files.exclude'];
981
- const excludePreferencesGlobs = Object.keys(excludePreferences).filter(key => !!excludePreferences[key]);
982
- return [...new Set([...excludePreferencesGlobs, ...excludeOptions || []])];
983
- }
984
- /**
985
- * Compare two normalized strings.
986
- *
987
- * @param a {string} the first string.
988
- * @param b {string} the second string.
989
- */
990
- compare(a, b) {
991
- const itemA = a.toLowerCase().trim();
992
- const itemB = b.toLowerCase().trim();
993
- return itemA.localeCompare(itemB);
994
- }
995
- /**
996
- * @param recursive if true, all child nodes will be included in the stringified result.
997
- */
998
- nodeToString(node, recursive) {
999
- if (SearchInWorkspaceFileNode.is(node) || SearchInWorkspaceRootFolderNode.is(node)) {
1000
- if (recursive) {
1001
- return this.nodeIteratorToString(new browser_1.TopDownTreeIterator(node, { pruneSiblings: true }));
1002
- }
1003
- return this.labelProvider.getLongName(node.uri);
1004
- }
1005
- if (SearchInWorkspaceResultLineNode.is(node)) {
1006
- return ` ${node.line}:${node.character}: ${node.lineText}`;
1007
- }
1008
- return '';
1009
- }
1010
- treeToString() {
1011
- return this.nodeIteratorToString(this.getVisibleNodes());
1012
- }
1013
- *getVisibleNodes() {
1014
- for (const { node } of this.rows.values()) {
1015
- yield node;
1016
- }
1017
- }
1018
- nodeIteratorToString(nodes) {
1019
- const strings = [];
1020
- for (const node of nodes) {
1021
- const string = this.nodeToString(node, false);
1022
- if (string.length !== 0) {
1023
- strings.push(string);
1024
- }
1025
- }
1026
- return strings.join(core_1.EOL);
1027
- }
1028
- };
1029
- __decorate([
1030
- (0, inversify_1.inject)(search_in_workspace_service_1.SearchInWorkspaceService),
1031
- __metadata("design:type", search_in_workspace_service_1.SearchInWorkspaceService)
1032
- ], SearchInWorkspaceResultTreeWidget.prototype, "searchService", void 0);
1033
- __decorate([
1034
- (0, inversify_1.inject)(browser_2.EditorManager),
1035
- __metadata("design:type", browser_2.EditorManager)
1036
- ], SearchInWorkspaceResultTreeWidget.prototype, "editorManager", void 0);
1037
- __decorate([
1038
- (0, inversify_1.inject)(browser_4.FileResourceResolver),
1039
- __metadata("design:type", browser_4.FileResourceResolver)
1040
- ], SearchInWorkspaceResultTreeWidget.prototype, "fileResourceResolver", void 0);
1041
- __decorate([
1042
- (0, inversify_1.inject)(browser_1.ApplicationShell),
1043
- __metadata("design:type", browser_1.ApplicationShell)
1044
- ], SearchInWorkspaceResultTreeWidget.prototype, "shell", void 0);
1045
- __decorate([
1046
- (0, inversify_1.inject)(browser_3.WorkspaceService),
1047
- __metadata("design:type", browser_3.WorkspaceService)
1048
- ], SearchInWorkspaceResultTreeWidget.prototype, "workspaceService", void 0);
1049
- __decorate([
1050
- (0, inversify_1.inject)(browser_1.TreeExpansionService),
1051
- __metadata("design:type", Object)
1052
- ], SearchInWorkspaceResultTreeWidget.prototype, "expansionService", void 0);
1053
- __decorate([
1054
- (0, inversify_1.inject)(search_in_workspace_preferences_1.SearchInWorkspacePreferences),
1055
- __metadata("design:type", Object)
1056
- ], SearchInWorkspaceResultTreeWidget.prototype, "searchInWorkspacePreferences", void 0);
1057
- __decorate([
1058
- (0, inversify_1.inject)(core_1.ProgressService),
1059
- __metadata("design:type", core_1.ProgressService)
1060
- ], SearchInWorkspaceResultTreeWidget.prototype, "progressService", void 0);
1061
- __decorate([
1062
- (0, inversify_1.inject)(color_registry_1.ColorRegistry),
1063
- __metadata("design:type", color_registry_1.ColorRegistry)
1064
- ], SearchInWorkspaceResultTreeWidget.prototype, "colorRegistry", void 0);
1065
- __decorate([
1066
- (0, inversify_1.inject)(browser_4.FileSystemPreferences),
1067
- __metadata("design:type", Object)
1068
- ], SearchInWorkspaceResultTreeWidget.prototype, "filesystemPreferences", void 0);
1069
- __decorate([
1070
- (0, inversify_1.inject)(file_service_1.FileService),
1071
- __metadata("design:type", file_service_1.FileService)
1072
- ], SearchInWorkspaceResultTreeWidget.prototype, "fileService", void 0);
1073
- __decorate([
1074
- (0, inversify_1.postConstruct)(),
1075
- __metadata("design:type", Function),
1076
- __metadata("design:paramtypes", []),
1077
- __metadata("design:returntype", void 0)
1078
- ], SearchInWorkspaceResultTreeWidget.prototype, "init", null);
1079
- SearchInWorkspaceResultTreeWidget = __decorate([
1080
- (0, inversify_1.injectable)(),
1081
- __param(0, (0, inversify_1.inject)(browser_1.TreeProps)),
1082
- __param(1, (0, inversify_1.inject)(browser_1.TreeModel)),
1083
- __param(2, (0, inversify_1.inject)(browser_1.ContextMenuRenderer)),
1084
- __metadata("design:paramtypes", [Object, Object, browser_1.ContextMenuRenderer])
1085
- ], SearchInWorkspaceResultTreeWidget);
1086
- exports.SearchInWorkspaceResultTreeWidget = SearchInWorkspaceResultTreeWidget;
1087
- (function (SearchInWorkspaceResultTreeWidget) {
1088
- let Menus;
1089
- (function (Menus) {
1090
- Menus.BASE = ['siw-tree-context-menu'];
1091
- /** Dismiss command, or others that only affect the widget itself */
1092
- Menus.INTERNAL = [...Menus.BASE, '1_internal'];
1093
- /** Copy a stringified representation of content */
1094
- Menus.COPY = [...Menus.BASE, '2_copy'];
1095
- /** Commands that lead out of the widget, like revealing a file in the navigator */
1096
- Menus.EXTERNAL = [...Menus.BASE, '3_external'];
1097
- })(Menus = SearchInWorkspaceResultTreeWidget.Menus || (SearchInWorkspaceResultTreeWidget.Menus = {}));
1098
- })(SearchInWorkspaceResultTreeWidget = exports.SearchInWorkspaceResultTreeWidget || (exports.SearchInWorkspaceResultTreeWidget = {}));
1099
- exports.SearchInWorkspaceResultTreeWidget = SearchInWorkspaceResultTreeWidget;
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2018 TypeFox and others.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
18
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
20
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
21
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
22
+ };
23
+ var __metadata = (this && this.__metadata) || function (k, v) {
24
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
25
+ };
26
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
27
+ return function (target, key) { decorator(target, key, paramIndex); }
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.SearchInWorkspaceResultTreeWidget = exports.SearchInWorkspaceResultLineNode = exports.SearchInWorkspaceFileNode = exports.SearchInWorkspaceRootFolderNode = exports.SearchInWorkspaceRoot = void 0;
31
+ const inversify_1 = require("@theia/core/shared/inversify");
32
+ const browser_1 = require("@theia/core/lib/browser");
33
+ const core_1 = require("@theia/core");
34
+ const browser_2 = require("@theia/editor/lib/browser");
35
+ const browser_3 = require("@theia/workspace/lib/browser");
36
+ const browser_4 = require("@theia/filesystem/lib/browser");
37
+ const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
38
+ const search_in_workspace_service_1 = require("./search-in-workspace-service");
39
+ const common_1 = require("@theia/core/lib/common");
40
+ const uri_1 = require("@theia/core/lib/common/uri");
41
+ const React = require("@theia/core/shared/react");
42
+ const search_in_workspace_preferences_1 = require("./search-in-workspace-preferences");
43
+ const color_registry_1 = require("@theia/core/lib/browser/color-registry");
44
+ const minimatch = require("minimatch");
45
+ const disposable_1 = require("@theia/core/lib/common/disposable");
46
+ const debounce = require("@theia/core/shared/lodash.debounce");
47
+ const nls_1 = require("@theia/core/lib/common/nls");
48
+ const ROOT_ID = 'ResultTree';
49
+ var SearchInWorkspaceRoot;
50
+ (function (SearchInWorkspaceRoot) {
51
+ function is(node) {
52
+ return browser_1.CompositeTreeNode.is(node) && node.id === ROOT_ID;
53
+ }
54
+ SearchInWorkspaceRoot.is = is;
55
+ })(SearchInWorkspaceRoot = exports.SearchInWorkspaceRoot || (exports.SearchInWorkspaceRoot = {}));
56
+ var SearchInWorkspaceRootFolderNode;
57
+ (function (SearchInWorkspaceRootFolderNode) {
58
+ function is(node) {
59
+ return browser_1.ExpandableTreeNode.is(node) && browser_1.SelectableTreeNode.is(node) && 'path' in node && 'folderUri' in node && !('fileUri' in node);
60
+ }
61
+ SearchInWorkspaceRootFolderNode.is = is;
62
+ })(SearchInWorkspaceRootFolderNode = exports.SearchInWorkspaceRootFolderNode || (exports.SearchInWorkspaceRootFolderNode = {}));
63
+ var SearchInWorkspaceFileNode;
64
+ (function (SearchInWorkspaceFileNode) {
65
+ function is(node) {
66
+ return browser_1.ExpandableTreeNode.is(node) && browser_1.SelectableTreeNode.is(node) && 'path' in node && 'fileUri' in node && !('folderUri' in node);
67
+ }
68
+ SearchInWorkspaceFileNode.is = is;
69
+ })(SearchInWorkspaceFileNode = exports.SearchInWorkspaceFileNode || (exports.SearchInWorkspaceFileNode = {}));
70
+ var SearchInWorkspaceResultLineNode;
71
+ (function (SearchInWorkspaceResultLineNode) {
72
+ function is(node) {
73
+ return browser_1.SelectableTreeNode.is(node) && 'line' in node && 'character' in node && 'lineText' in node;
74
+ }
75
+ SearchInWorkspaceResultLineNode.is = is;
76
+ })(SearchInWorkspaceResultLineNode = exports.SearchInWorkspaceResultLineNode || (exports.SearchInWorkspaceResultLineNode = {}));
77
+ let SearchInWorkspaceResultTreeWidget = class SearchInWorkspaceResultTreeWidget extends browser_1.TreeWidget {
78
+ constructor(props, model, contextMenuRenderer) {
79
+ super(props, model, contextMenuRenderer);
80
+ this._showReplaceButtons = false;
81
+ this._replaceTerm = '';
82
+ this.searchTerm = '';
83
+ this.startSearchOnModification = (activeEditor) => debounce(() => this.searchActiveEditor(activeEditor, this.searchTerm, this.searchOptions), this.searchOnEditorModificationDelay);
84
+ this.searchOnEditorModificationDelay = 300;
85
+ this.toDisposeOnActiveEditorChanged = new disposable_1.DisposableCollection();
86
+ // The default root name to add external search results in the case that a workspace is opened.
87
+ this.defaultRootName = nls_1.nls.localizeByDefault('Other files');
88
+ this.forceVisibleRootNode = false;
89
+ this.appliedDecorations = new Map();
90
+ this.changeEmitter = new core_1.Emitter();
91
+ this.onExpansionChangedEmitter = new core_1.Emitter();
92
+ this.onExpansionChanged = this.onExpansionChangedEmitter.event;
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ this.focusInputEmitter = new core_1.Emitter();
95
+ this.remove = (node, e) => this.doRemove(node, e);
96
+ model.root = {
97
+ id: ROOT_ID,
98
+ parent: undefined,
99
+ visible: false,
100
+ children: []
101
+ };
102
+ this.toDispose.push(model.onSelectionChanged(nodes => {
103
+ const node = nodes[0];
104
+ if (SearchInWorkspaceResultLineNode.is(node)) {
105
+ this.doOpen(node, true, true);
106
+ }
107
+ }));
108
+ this.toDispose.push(model.onOpenNode(node => {
109
+ if (SearchInWorkspaceResultLineNode.is(node)) {
110
+ this.doOpen(node, true, false);
111
+ }
112
+ }));
113
+ this.resultTree = new Map();
114
+ this.toDispose.push(model.onNodeRefreshed(() => this.changeEmitter.fire(this.resultTree)));
115
+ }
116
+ init() {
117
+ super.init();
118
+ this.addClass('resultContainer');
119
+ this.toDispose.push(this.changeEmitter);
120
+ this.toDispose.push(this.focusInputEmitter);
121
+ this.toDispose.push(this.editorManager.onActiveEditorChanged(activeEditor => {
122
+ this.updateCurrentEditorDecorations();
123
+ this.toDisposeOnActiveEditorChanged.dispose();
124
+ this.toDispose.push(this.toDisposeOnActiveEditorChanged);
125
+ if (activeEditor) {
126
+ this.toDisposeOnActiveEditorChanged.push(activeEditor.editor.onDocumentContentChanged(() => {
127
+ if (this.searchTerm !== '' && this.searchInWorkspacePreferences['search.searchOnEditorModification']) {
128
+ this.startSearchOnModification(activeEditor)();
129
+ }
130
+ }));
131
+ }
132
+ }));
133
+ this.toDispose.push(this.searchInWorkspacePreferences.onPreferenceChanged(() => {
134
+ this.update();
135
+ }));
136
+ this.toDispose.push(this.fileService.onDidFilesChange(event => {
137
+ if (event.gotDeleted()) {
138
+ event.getDeleted().forEach(deletedFile => {
139
+ const fileNodes = this.getFileNodesByUri(deletedFile.resource);
140
+ fileNodes.forEach(node => this.removeFileNode(node));
141
+ });
142
+ this.model.refresh();
143
+ }
144
+ }));
145
+ this.toDispose.push(this.model.onExpansionChanged(() => {
146
+ this.onExpansionChangedEmitter.fire(undefined);
147
+ }));
148
+ }
149
+ get fileNumber() {
150
+ let num = 0;
151
+ for (const rootFolderNode of this.resultTree.values()) {
152
+ num += rootFolderNode.children.length;
153
+ }
154
+ return num;
155
+ }
156
+ set showReplaceButtons(srb) {
157
+ this._showReplaceButtons = srb;
158
+ this.update();
159
+ }
160
+ set replaceTerm(rt) {
161
+ this._replaceTerm = rt;
162
+ this.update();
163
+ }
164
+ get isReplacing() {
165
+ return this._replaceTerm !== '' && this._showReplaceButtons;
166
+ }
167
+ get onChange() {
168
+ return this.changeEmitter.event;
169
+ }
170
+ get onFocusInput() {
171
+ return this.focusInputEmitter.event;
172
+ }
173
+ collapseAll() {
174
+ for (const rootFolderNode of this.resultTree.values()) {
175
+ for (const fileNode of rootFolderNode.children) {
176
+ this.expansionService.collapseNode(fileNode);
177
+ }
178
+ if (rootFolderNode.visible) {
179
+ this.expansionService.collapseNode(rootFolderNode);
180
+ }
181
+ }
182
+ }
183
+ expandAll() {
184
+ for (const rootFolderNode of this.resultTree.values()) {
185
+ for (const fileNode of rootFolderNode.children) {
186
+ this.expansionService.expandNode(fileNode);
187
+ }
188
+ if (rootFolderNode.visible) {
189
+ this.expansionService.expandNode(rootFolderNode);
190
+ }
191
+ }
192
+ }
193
+ areResultsCollapsed() {
194
+ for (const rootFolderNode of this.resultTree.values()) {
195
+ for (const fileNode of rootFolderNode.children) {
196
+ if (!browser_1.ExpandableTreeNode.isCollapsed(fileNode)) {
197
+ return false;
198
+ }
199
+ }
200
+ }
201
+ return true;
202
+ }
203
+ selectNextResult() {
204
+ if (!this.model.getFocusedNode()) {
205
+ return this.selectFirstResult();
206
+ }
207
+ let foundNextResult = false;
208
+ while (!foundNextResult) {
209
+ const nextNode = this.model.getNextNode();
210
+ if (!nextNode) {
211
+ return this.selectFirstResult();
212
+ }
213
+ else if (SearchInWorkspaceResultLineNode.is(nextNode)) {
214
+ foundNextResult = true;
215
+ this.selectExpandOpenResultNode(nextNode);
216
+ }
217
+ else {
218
+ this.model.selectNext();
219
+ }
220
+ }
221
+ }
222
+ selectPreviousResult() {
223
+ if (!this.model.getFocusedNode()) {
224
+ return this.selectLastResult();
225
+ }
226
+ let foundSelectedNode = false;
227
+ while (!foundSelectedNode) {
228
+ const prevNode = this.model.getPrevNode();
229
+ if (!prevNode) {
230
+ return this.selectLastResult();
231
+ }
232
+ else if (SearchInWorkspaceResultLineNode.is(prevNode)) {
233
+ foundSelectedNode = true;
234
+ this.selectExpandOpenResultNode(prevNode);
235
+ }
236
+ else if (prevNode.id === 'ResultTree') {
237
+ return this.selectLastResult();
238
+ }
239
+ else {
240
+ this.model.selectPrev();
241
+ }
242
+ }
243
+ }
244
+ selectExpandOpenResultNode(node) {
245
+ this.model.expandNode(node.parent.parent);
246
+ this.model.expandNode(node.parent);
247
+ this.model.selectNode(node);
248
+ this.model.openNode(node);
249
+ }
250
+ selectFirstResult() {
251
+ for (const rootFolder of this.resultTree.values()) {
252
+ for (const file of rootFolder.children) {
253
+ for (const result of file.children) {
254
+ if (browser_1.SelectableTreeNode.is(result)) {
255
+ return this.selectExpandOpenResultNode(result);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ selectLastResult() {
262
+ const rootFolders = Array.from(this.resultTree.values());
263
+ for (let i = rootFolders.length - 1; i >= 0; i--) {
264
+ const rootFolder = rootFolders[i];
265
+ for (let j = rootFolder.children.length - 1; j >= 0; j--) {
266
+ const file = rootFolder.children[j];
267
+ for (let k = file.children.length - 1; k >= 0; k--) {
268
+ const result = file.children[k];
269
+ if (browser_1.SelectableTreeNode.is(result)) {
270
+ return this.selectExpandOpenResultNode(result);
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+ /**
277
+ * Find matches for the given editor.
278
+ * @param searchTerm the search term.
279
+ * @param widget the editor widget.
280
+ * @param searchOptions the search options to apply.
281
+ *
282
+ * @returns the list of matches.
283
+ */
284
+ findMatches(searchTerm, widget, searchOptions) {
285
+ if (!widget.editor.document.findMatches) {
286
+ return [];
287
+ }
288
+ const results = widget.editor.document.findMatches({
289
+ searchString: searchTerm,
290
+ isRegex: !!searchOptions.useRegExp,
291
+ matchCase: !!searchOptions.matchCase,
292
+ matchWholeWord: !!searchOptions.matchWholeWord,
293
+ limitResultCount: searchOptions.maxResults
294
+ });
295
+ const matches = [];
296
+ results.forEach(r => {
297
+ const numberOfLines = searchTerm.split('\n').length;
298
+ const lineTexts = [];
299
+ for (let i = 0; i < numberOfLines; i++) {
300
+ lineTexts.push(widget.editor.document.getLineContent(r.range.start.line + i));
301
+ }
302
+ matches.push({
303
+ line: r.range.start.line,
304
+ character: r.range.start.character,
305
+ length: searchTerm.length,
306
+ lineText: lineTexts.join('\n')
307
+ });
308
+ });
309
+ return matches;
310
+ }
311
+ /**
312
+ * Convert a pattern to match all directories.
313
+ * @param workspaceRootUri the uri of the current workspace root.
314
+ * @param pattern the pattern to be converted.
315
+ */
316
+ convertPatternToGlob(workspaceRootUri, pattern) {
317
+ if (pattern.startsWith('**/')) {
318
+ return pattern;
319
+ }
320
+ if (pattern.startsWith('./')) {
321
+ if (workspaceRootUri === undefined) {
322
+ return pattern;
323
+ }
324
+ return workspaceRootUri.toString() + pattern.replace('./', '/');
325
+ }
326
+ return pattern.startsWith('/')
327
+ ? '**' + pattern
328
+ : '**/' + pattern;
329
+ }
330
+ /**
331
+ * Determine if the URI matches any of the patterns.
332
+ * @param uri the editor URI.
333
+ * @param patterns the glob patterns to verify.
334
+ */
335
+ inPatternList(uri, patterns) {
336
+ const opts = { dot: true, matchBase: true };
337
+ return patterns.some(pattern => minimatch(uri.toString(), this.convertPatternToGlob(this.workspaceService.getWorkspaceRootUri(uri), pattern), opts));
338
+ }
339
+ /**
340
+ * Determine if the given editor satisfies the filtering criteria.
341
+ * An editor should be searched only if:
342
+ * - it is not excluded through the `excludes` list.
343
+ * - it is not explicitly present in a non-empty `includes` list.
344
+ */
345
+ shouldApplySearch(editorWidget, searchOptions) {
346
+ const excludePatterns = this.getExcludeGlobs(searchOptions.exclude);
347
+ if (this.inPatternList(editorWidget.editor.uri, excludePatterns)) {
348
+ return false;
349
+ }
350
+ const includePatterns = searchOptions.include;
351
+ if (!!(includePatterns === null || includePatterns === void 0 ? void 0 : includePatterns.length) && !this.inPatternList(editorWidget.editor.uri, includePatterns)) {
352
+ return false;
353
+ }
354
+ return true;
355
+ }
356
+ /**
357
+ * Search the active editor only and update the tree with those results.
358
+ */
359
+ searchActiveEditor(activeEditor, searchTerm, searchOptions) {
360
+ const includesExternalResults = () => !!this.resultTree.get(this.defaultRootName);
361
+ // Check if outside workspace results are present before searching.
362
+ const hasExternalResultsBefore = includesExternalResults();
363
+ // Collect search results for the given editor.
364
+ const results = this.searchInEditor(activeEditor, searchTerm, searchOptions);
365
+ // Update the tree by removing the result node, and add new results if applicable.
366
+ this.getFileNodesByUri(activeEditor.editor.uri).forEach(fileNode => this.removeFileNode(fileNode));
367
+ if (results) {
368
+ this.appendToResultTree(results);
369
+ }
370
+ // Check if outside workspace results are present after searching.
371
+ const hasExternalResultsAfter = includesExternalResults();
372
+ // Redo a search to update the tree node visibility if:
373
+ // + `Other files` node was present, now it is not.
374
+ // + `Other files` node was not present, now it is.
375
+ if (hasExternalResultsBefore ? !hasExternalResultsAfter : hasExternalResultsAfter) {
376
+ this.search(this.searchTerm, this.searchOptions);
377
+ return;
378
+ }
379
+ this.handleSearchCompleted();
380
+ }
381
+ /**
382
+ * Perform a search in all open editors.
383
+ * @param searchTerm the search term.
384
+ * @param searchOptions the search options to apply.
385
+ *
386
+ * @returns the tuple of result count, and the list of search results.
387
+ */
388
+ searchInOpenEditors(searchTerm, searchOptions) {
389
+ // Track the number of results found.
390
+ let numberOfResults = 0;
391
+ const searchResults = [];
392
+ this.editorManager.all.forEach(e => {
393
+ const editorResults = this.searchInEditor(e, searchTerm, searchOptions);
394
+ if (editorResults) {
395
+ numberOfResults += editorResults.matches.length;
396
+ searchResults.push(editorResults);
397
+ }
398
+ });
399
+ return {
400
+ numberOfResults,
401
+ matches: searchResults
402
+ };
403
+ }
404
+ /**
405
+ * Perform a search in the target editor.
406
+ * @param editorWidget the editor widget.
407
+ * @param searchTerm the search term.
408
+ * @param searchOptions the search options to apply.
409
+ *
410
+ * @returns the search results from the given editor, undefined if the editor is either filtered or has no matches found.
411
+ */
412
+ searchInEditor(editorWidget, searchTerm, searchOptions) {
413
+ var _a;
414
+ if (!this.shouldApplySearch(editorWidget, searchOptions)) {
415
+ return undefined;
416
+ }
417
+ const matches = this.findMatches(searchTerm, editorWidget, searchOptions);
418
+ if (matches.length <= 0) {
419
+ return undefined;
420
+ }
421
+ const fileUri = editorWidget.editor.uri.toString();
422
+ const root = (_a = this.workspaceService.getWorkspaceRootUri(editorWidget.editor.uri)) === null || _a === void 0 ? void 0 : _a.toString();
423
+ return {
424
+ root: root !== null && root !== void 0 ? root : this.defaultRootName,
425
+ fileUri,
426
+ matches
427
+ };
428
+ }
429
+ /**
430
+ * Append search results to the result tree.
431
+ * @param result Search result.
432
+ */
433
+ appendToResultTree(result) {
434
+ const collapseValue = this.searchInWorkspacePreferences['search.collapseResults'];
435
+ let path;
436
+ if (result.root === this.defaultRootName) {
437
+ path = new uri_1.default(result.fileUri).path.dir.fsPath();
438
+ }
439
+ else {
440
+ path = this.filenameAndPath(result.root, result.fileUri).path;
441
+ }
442
+ const tree = this.resultTree;
443
+ let rootFolderNode = tree.get(result.root);
444
+ if (!rootFolderNode) {
445
+ rootFolderNode = this.createRootFolderNode(result.root);
446
+ tree.set(result.root, rootFolderNode);
447
+ }
448
+ let fileNode = rootFolderNode.children.find(f => f.fileUri === result.fileUri);
449
+ if (!fileNode) {
450
+ fileNode = this.createFileNode(result.root, path, result.fileUri, rootFolderNode);
451
+ rootFolderNode.children.push(fileNode);
452
+ }
453
+ for (const match of result.matches) {
454
+ const line = this.createResultLineNode(result, match, fileNode);
455
+ if (fileNode.children.findIndex(lineNode => lineNode.id === line.id) < 0) {
456
+ fileNode.children.push(line);
457
+ }
458
+ }
459
+ this.collapseFileNode(fileNode, collapseValue);
460
+ }
461
+ /**
462
+ * Handle when searching completed.
463
+ */
464
+ handleSearchCompleted(cancelIndicator) {
465
+ if (cancelIndicator) {
466
+ cancelIndicator.cancel();
467
+ }
468
+ this.sortResultTree();
469
+ this.refreshModelChildren();
470
+ }
471
+ /**
472
+ * Sort the result tree by URIs.
473
+ */
474
+ sortResultTree() {
475
+ // Sort the result map by folder URI.
476
+ const entries = [...this.resultTree.entries()];
477
+ entries.sort(([, a], [, b]) => this.compare(a.folderUri, b.folderUri));
478
+ this.resultTree = new Map(entries);
479
+ // Update the list of children nodes, sorting them by their file URI.
480
+ entries.forEach(([, folder]) => {
481
+ folder.children.sort((a, b) => this.compare(a.fileUri, b.fileUri));
482
+ });
483
+ }
484
+ /**
485
+ * Search and populate the result tree with matches.
486
+ * @param searchTerm the search term.
487
+ * @param searchOptions the search options to apply.
488
+ */
489
+ async search(searchTerm, searchOptions) {
490
+ this.searchTerm = searchTerm;
491
+ this.searchOptions = searchOptions;
492
+ searchOptions = {
493
+ ...searchOptions,
494
+ exclude: this.getExcludeGlobs(searchOptions.exclude)
495
+ };
496
+ this.resultTree.clear();
497
+ this.forceVisibleRootNode = false;
498
+ if (this.cancelIndicator) {
499
+ this.cancelIndicator.cancel();
500
+ }
501
+ if (searchTerm === '') {
502
+ this.refreshModelChildren();
503
+ return;
504
+ }
505
+ this.cancelIndicator = new core_1.CancellationTokenSource();
506
+ const cancelIndicator = this.cancelIndicator;
507
+ const token = this.cancelIndicator.token;
508
+ const progress = await this.progressService.showProgress({ text: `search: ${searchTerm}`, options: { location: 'search' } });
509
+ token.onCancellationRequested(() => {
510
+ progress.cancel();
511
+ if (searchId) {
512
+ this.searchService.cancel(searchId);
513
+ }
514
+ this.cancelIndicator = undefined;
515
+ this.changeEmitter.fire(this.resultTree);
516
+ });
517
+ // Collect search results for opened editors which otherwise may not be found by ripgrep (ex: dirty editors).
518
+ const { numberOfResults, matches } = this.searchInOpenEditors(searchTerm, searchOptions);
519
+ // The root node is visible if outside workspace results are found and workspace root(s) are present.
520
+ this.forceVisibleRootNode = matches.some(m => m.root === this.defaultRootName) && this.workspaceService.opened;
521
+ matches.forEach(m => this.appendToResultTree(m));
522
+ // Exclude files already covered by searching open editors.
523
+ this.editorManager.all.forEach(e => {
524
+ const excludePath = e.editor.uri.path.toString();
525
+ searchOptions.exclude = searchOptions.exclude ? searchOptions.exclude.concat(excludePath) : [excludePath];
526
+ });
527
+ // Reduce `maxResults` due to editor results.
528
+ if (searchOptions.maxResults) {
529
+ searchOptions.maxResults -= numberOfResults;
530
+ }
531
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
532
+ let pendingRefreshTimeout;
533
+ const searchId = await this.searchService.search(searchTerm, {
534
+ onResult: (aSearchId, result) => {
535
+ if (token.isCancellationRequested || aSearchId !== searchId) {
536
+ return;
537
+ }
538
+ this.appendToResultTree(result);
539
+ if (pendingRefreshTimeout) {
540
+ clearTimeout(pendingRefreshTimeout);
541
+ }
542
+ pendingRefreshTimeout = setTimeout(() => this.refreshModelChildren(), 100);
543
+ },
544
+ onDone: () => {
545
+ this.handleSearchCompleted(cancelIndicator);
546
+ }
547
+ }, searchOptions).catch(() => {
548
+ this.handleSearchCompleted(cancelIndicator);
549
+ });
550
+ }
551
+ focusFirstResult() {
552
+ if (SearchInWorkspaceRoot.is(this.model.root) && this.model.root.children.length > 0) {
553
+ const node = this.model.root.children[0];
554
+ if (browser_1.SelectableTreeNode.is(node)) {
555
+ this.node.focus();
556
+ this.model.selectNode(node);
557
+ }
558
+ }
559
+ }
560
+ /**
561
+ * Collapse the search-in-workspace file node
562
+ * based on the preference value.
563
+ */
564
+ collapseFileNode(node, preferenceValue) {
565
+ if (preferenceValue === 'auto' && node.children.length >= 10) {
566
+ node.expanded = false;
567
+ }
568
+ else if (preferenceValue === 'alwaysCollapse') {
569
+ node.expanded = false;
570
+ }
571
+ else if (preferenceValue === 'alwaysExpand') {
572
+ node.expanded = true;
573
+ }
574
+ }
575
+ handleUp(event) {
576
+ if (!this.model.getPrevSelectableNode(this.model.getFocusedNode())) {
577
+ this.focusInputEmitter.fire(true);
578
+ }
579
+ else {
580
+ super.handleUp(event);
581
+ }
582
+ }
583
+ async refreshModelChildren() {
584
+ if (SearchInWorkspaceRoot.is(this.model.root)) {
585
+ this.model.root.children = Array.from(this.resultTree.values());
586
+ this.model.refresh();
587
+ this.updateCurrentEditorDecorations();
588
+ }
589
+ }
590
+ updateCurrentEditorDecorations() {
591
+ this.shell.allTabBars.forEach(tb => {
592
+ const currentTitle = tb.currentTitle;
593
+ if (currentTitle && currentTitle.owner instanceof browser_2.EditorWidget) {
594
+ const widget = currentTitle.owner;
595
+ const fileNodes = this.getFileNodesByUri(widget.editor.uri);
596
+ if (fileNodes.length > 0) {
597
+ fileNodes.forEach(node => {
598
+ this.decorateEditor(node, widget);
599
+ });
600
+ }
601
+ else {
602
+ this.decorateEditor(undefined, widget);
603
+ }
604
+ }
605
+ });
606
+ const currentWidget = this.editorManager.currentEditor;
607
+ if (currentWidget) {
608
+ const fileNodes = this.getFileNodesByUri(currentWidget.editor.uri);
609
+ fileNodes.forEach(node => {
610
+ this.decorateEditor(node, currentWidget);
611
+ });
612
+ }
613
+ }
614
+ createRootFolderNode(rootUri) {
615
+ const uri = new uri_1.default(rootUri);
616
+ return {
617
+ selected: false,
618
+ path: uri.path.fsPath(),
619
+ folderUri: rootUri,
620
+ uri: new uri_1.default(rootUri),
621
+ children: [],
622
+ expanded: true,
623
+ id: rootUri,
624
+ parent: this.model.root,
625
+ visible: this.forceVisibleRootNode || this.workspaceService.isMultiRootWorkspaceOpened
626
+ };
627
+ }
628
+ createFileNode(rootUri, path, fileUri, parent) {
629
+ return {
630
+ selected: false,
631
+ path,
632
+ children: [],
633
+ expanded: true,
634
+ id: `${rootUri}::${fileUri}`,
635
+ parent,
636
+ fileUri,
637
+ uri: new uri_1.default(fileUri),
638
+ };
639
+ }
640
+ createResultLineNode(result, match, fileNode) {
641
+ return {
642
+ ...result,
643
+ ...match,
644
+ selected: false,
645
+ id: result.fileUri + '-' + match.line + '-' + match.character + '-' + match.length,
646
+ name: typeof match.lineText === 'string' ? match.lineText : match.lineText.text,
647
+ parent: fileNode
648
+ };
649
+ }
650
+ getFileNodesByUri(uri) {
651
+ const nodes = [];
652
+ const fileUri = uri.withScheme('file').toString();
653
+ for (const rootFolderNode of this.resultTree.values()) {
654
+ const rootUri = new uri_1.default(rootFolderNode.path).withScheme('file');
655
+ if (rootUri.isEqualOrParent(uri) || rootFolderNode.id === this.defaultRootName) {
656
+ for (const fileNode of rootFolderNode.children) {
657
+ if (fileNode.fileUri === fileUri) {
658
+ nodes.push(fileNode);
659
+ }
660
+ }
661
+ }
662
+ }
663
+ return nodes;
664
+ }
665
+ filenameAndPath(rootUriStr, uriStr) {
666
+ const uri = new uri_1.default(uriStr);
667
+ const relativePath = new uri_1.default(rootUriStr).relative(uri.parent);
668
+ return {
669
+ name: this.labelProvider.getName(uri),
670
+ path: relativePath ? relativePath.fsPath() : ''
671
+ };
672
+ }
673
+ getDepthPadding(depth) {
674
+ return super.getDepthPadding(depth) + 5;
675
+ }
676
+ renderCaption(node, props) {
677
+ if (SearchInWorkspaceRootFolderNode.is(node)) {
678
+ return this.renderRootFolderNode(node);
679
+ }
680
+ else if (SearchInWorkspaceFileNode.is(node)) {
681
+ return this.renderFileNode(node);
682
+ }
683
+ else if (SearchInWorkspaceResultLineNode.is(node)) {
684
+ return this.renderResultLineNode(node);
685
+ }
686
+ return '';
687
+ }
688
+ renderTailDecorations(node, props) {
689
+ return React.createElement("div", { className: 'result-node-buttons' },
690
+ this._showReplaceButtons && this.renderReplaceButton(node),
691
+ this.renderRemoveButton(node));
692
+ }
693
+ doReplace(node, e) {
694
+ const selection = browser_1.SelectableTreeNode.isSelected(node) ? this.selectionService.selection : [node];
695
+ selection.forEach(n => this.replace(n));
696
+ e.stopPropagation();
697
+ }
698
+ renderReplaceButton(node) {
699
+ const isResultLineNode = SearchInWorkspaceResultLineNode.is(node);
700
+ return React.createElement("span", { className: isResultLineNode ? (0, browser_1.codicon)('replace') : (0, browser_1.codicon)('replace-all'), onClick: e => this.doReplace(node, e), title: isResultLineNode
701
+ ? nls_1.nls.localizeByDefault('Replace')
702
+ : nls_1.nls.localizeByDefault('Replace All') });
703
+ }
704
+ getFileCount(node) {
705
+ if (SearchInWorkspaceRoot.is(node)) {
706
+ return node.children.reduce((acc, current) => acc + this.getFileCount(current), 0);
707
+ }
708
+ else if (SearchInWorkspaceRootFolderNode.is(node)) {
709
+ return node.children.length;
710
+ }
711
+ else if (SearchInWorkspaceFileNode.is(node)) {
712
+ return 1;
713
+ }
714
+ return 0;
715
+ }
716
+ getResultCount(node) {
717
+ if (SearchInWorkspaceRoot.is(node)) {
718
+ return node.children.reduce((acc, current) => acc + this.getResultCount(current), 0);
719
+ }
720
+ else if (SearchInWorkspaceRootFolderNode.is(node)) {
721
+ return node.children.reduce((acc, current) => acc + this.getResultCount(current), 0);
722
+ }
723
+ else if (SearchInWorkspaceFileNode.is(node)) {
724
+ return node.children.length;
725
+ }
726
+ else if (SearchInWorkspaceResultLineNode.is(node)) {
727
+ return 1;
728
+ }
729
+ return 0;
730
+ }
731
+ /**
732
+ * Replace results under the node passed into the function. If node is undefined, replace all results.
733
+ * @param node Node in the tree widget where the "replace all" operation is performed
734
+ */
735
+ async replace(node) {
736
+ const replaceForNode = node || this.model.root;
737
+ const needConfirm = !SearchInWorkspaceFileNode.is(node) && !SearchInWorkspaceResultLineNode.is(node);
738
+ const replacementText = this._replaceTerm;
739
+ if (!needConfirm || await this.confirmReplaceAll(this.getResultCount(replaceForNode), this.getFileCount(replaceForNode), replacementText)) {
740
+ (node ? [node] : Array.from(this.resultTree.values())).forEach(n => {
741
+ this.replaceResult(n, !!node, replacementText);
742
+ this.removeNode(n);
743
+ });
744
+ }
745
+ }
746
+ confirmReplaceAll(resultNumber, fileNumber, replacementText) {
747
+ return new browser_1.ConfirmDialog({
748
+ title: nls_1.nls.localizeByDefault('Replace All'),
749
+ msg: this.buildReplaceAllConfirmationMessage(resultNumber, fileNumber, replacementText)
750
+ }).open();
751
+ }
752
+ buildReplaceAllConfirmationMessage(occurrences, fileCount, replaceValue) {
753
+ if (occurrences === 1) {
754
+ if (fileCount === 1) {
755
+ if (replaceValue) {
756
+ return nls_1.nls.localizeByDefault("Replace {0} occurrence across {1} file with '{2}'?", occurrences, fileCount, replaceValue);
757
+ }
758
+ return nls_1.nls.localizeByDefault('Replace {0} occurrence across {1} file?', occurrences, fileCount);
759
+ }
760
+ if (replaceValue) {
761
+ return nls_1.nls.localizeByDefault("Replace {0} occurrence across {1} files with '{2}'?", occurrences, fileCount, replaceValue);
762
+ }
763
+ return nls_1.nls.localizeByDefault('Replace {0} occurrence across {1} files?', occurrences, fileCount);
764
+ }
765
+ if (fileCount === 1) {
766
+ if (replaceValue) {
767
+ return nls_1.nls.localizeByDefault("Replace {0} occurrences across {1} file with '{2}'?", occurrences, fileCount, replaceValue);
768
+ }
769
+ return nls_1.nls.localizeByDefault('Replace {0} occurrences across {1} file?', occurrences, fileCount);
770
+ }
771
+ if (replaceValue) {
772
+ return nls_1.nls.localizeByDefault("Replace {0} occurrences across {1} files with '{2}'?", occurrences, fileCount, replaceValue);
773
+ }
774
+ return nls_1.nls.localizeByDefault('Replace {0} occurrences across {1} files?', occurrences, fileCount);
775
+ }
776
+ updateRightResults(node) {
777
+ const fileNode = node.parent;
778
+ const rightPositionedNodes = fileNode.children.filter(rl => rl.line === node.line && rl.character > node.character);
779
+ const diff = this._replaceTerm.length - this.searchTerm.length;
780
+ rightPositionedNodes.forEach(r => r.character += diff);
781
+ }
782
+ /**
783
+ * Replace text either in all search matches under a node or in all search matches, and save the changes.
784
+ * @param node - node in the tree widget in which the "replace all" is performed.
785
+ * @param {boolean} replaceOne - whether the function is to replace all matches under a node. If it is false, replace all.
786
+ * @param replacementText - text to be used for all replacements in the current replacement cycle.
787
+ */
788
+ async replaceResult(node, replaceOne, replacementText) {
789
+ const toReplace = [];
790
+ if (SearchInWorkspaceRootFolderNode.is(node)) {
791
+ node.children.forEach(fileNode => this.replaceResult(fileNode, replaceOne, replacementText));
792
+ }
793
+ else if (SearchInWorkspaceFileNode.is(node)) {
794
+ toReplace.push(...node.children);
795
+ }
796
+ else if (SearchInWorkspaceResultLineNode.is(node)) {
797
+ toReplace.push(node);
798
+ this.updateRightResults(node);
799
+ }
800
+ if (toReplace.length > 0) {
801
+ // Store the state of all tracked editors before another editor widget might be created for text replacing.
802
+ const trackedEditors = this.editorManager.all;
803
+ // Open the file only if the function is called to replace all matches under a specific node.
804
+ const widget = replaceOne ? await this.doOpen(toReplace[0]) : await this.doGetWidget(toReplace[0]);
805
+ const source = widget.editor.document.getText();
806
+ const replaceOperations = toReplace.map(resultLineNode => ({
807
+ text: replacementText,
808
+ range: {
809
+ start: {
810
+ line: resultLineNode.line - 1,
811
+ character: resultLineNode.character - 1
812
+ },
813
+ end: this.findEndCharacterPosition(resultLineNode),
814
+ }
815
+ }));
816
+ // Replace the text.
817
+ await widget.editor.replaceText({
818
+ source,
819
+ replaceOperations
820
+ });
821
+ // Save the text replacement changes in the editor.
822
+ await widget.saveable.save();
823
+ // Dispose the widget if it is not opened but created for `replaceAll`.
824
+ if (!replaceOne) {
825
+ if (trackedEditors.indexOf(widget) === -1) {
826
+ widget.dispose();
827
+ }
828
+ }
829
+ }
830
+ }
831
+ doRemove(node, e) {
832
+ const selection = browser_1.SelectableTreeNode.isSelected(node) ? this.selectionService.selection : [node];
833
+ selection.forEach(n => this.removeNode(n));
834
+ e.stopPropagation();
835
+ }
836
+ renderRemoveButton(node) {
837
+ return React.createElement("span", { className: (0, browser_1.codicon)('close'), onClick: e => this.remove(node, e), title: 'Dismiss' });
838
+ }
839
+ removeNode(node) {
840
+ if (SearchInWorkspaceRootFolderNode.is(node)) {
841
+ this.removeRootFolderNode(node);
842
+ }
843
+ else if (SearchInWorkspaceFileNode.is(node)) {
844
+ this.removeFileNode(node);
845
+ }
846
+ else if (SearchInWorkspaceResultLineNode.is(node)) {
847
+ this.removeResultLineNode(node);
848
+ }
849
+ this.refreshModelChildren();
850
+ }
851
+ removeRootFolderNode(node) {
852
+ for (const rootUri of this.resultTree.keys()) {
853
+ if (rootUri === node.folderUri) {
854
+ this.resultTree.delete(rootUri);
855
+ break;
856
+ }
857
+ }
858
+ }
859
+ removeFileNode(node) {
860
+ const rootFolderNode = node.parent;
861
+ const index = rootFolderNode.children.findIndex(fileNode => fileNode.id === node.id);
862
+ if (index > -1) {
863
+ rootFolderNode.children.splice(index, 1);
864
+ }
865
+ if (this.getFileCount(rootFolderNode) === 0) {
866
+ this.removeRootFolderNode(rootFolderNode);
867
+ }
868
+ }
869
+ removeResultLineNode(node) {
870
+ const fileNode = node.parent;
871
+ const index = fileNode.children.findIndex(n => n.fileUri === node.fileUri && n.line === node.line && n.character === node.character);
872
+ if (index > -1) {
873
+ fileNode.children.splice(index, 1);
874
+ if (this.getResultCount(fileNode) === 0) {
875
+ this.removeFileNode(fileNode);
876
+ }
877
+ }
878
+ }
879
+ findEndCharacterPosition(node) {
880
+ const lineText = typeof node.lineText === 'string' ? node.lineText : node.lineText.text;
881
+ const lines = lineText.split('\n');
882
+ const line = node.line + lines.length - 2;
883
+ let character = node.character - 1 + node.length;
884
+ if (lines.length > 1) {
885
+ character = node.length - lines[0].length + node.character - lines.length;
886
+ if (lines.length > 2) {
887
+ for (const lineNum of Array(lines.length - 2).keys()) {
888
+ character -= lines[lineNum + 1].length;
889
+ }
890
+ }
891
+ }
892
+ return { line, character };
893
+ }
894
+ renderRootFolderNode(node) {
895
+ return React.createElement("div", { className: 'result' },
896
+ React.createElement("div", { className: 'result-head' },
897
+ React.createElement("div", { className: `result-head-info noWrapInfo noselect ${node.selected ? 'selected' : ''}` },
898
+ React.createElement("span", { className: `file-icon ${this.toNodeIcon(node) || ''}` }),
899
+ React.createElement("div", { className: 'noWrapInfo' },
900
+ React.createElement("span", { className: 'file-name' }, this.toNodeName(node)),
901
+ node.path !== '/' + this.defaultRootName &&
902
+ React.createElement("span", { className: 'file-path ' + browser_1.TREE_NODE_INFO_CLASS }, node.path))),
903
+ React.createElement("span", { className: 'notification-count-container highlighted-count-container' },
904
+ React.createElement("span", { className: 'notification-count' }, this.getFileCount(node)))));
905
+ }
906
+ renderFileNode(node) {
907
+ return React.createElement("div", { className: 'result' },
908
+ React.createElement("div", { className: 'result-head' },
909
+ React.createElement("div", { className: `result-head-info noWrapInfo noselect ${node.selected ? 'selected' : ''}`, title: new uri_1.default(node.fileUri).path.fsPath() },
910
+ React.createElement("span", { className: `file-icon ${this.toNodeIcon(node)}` }),
911
+ React.createElement("div", { className: 'noWrapInfo' },
912
+ React.createElement("span", { className: 'file-name' }, this.toNodeName(node)),
913
+ React.createElement("span", { className: 'file-path ' + browser_1.TREE_NODE_INFO_CLASS }, node.path))),
914
+ React.createElement("span", { className: 'notification-count-container' },
915
+ React.createElement("span", { className: 'notification-count' }, this.getResultCount(node)))));
916
+ }
917
+ renderResultLineNode(node) {
918
+ const character = typeof node.lineText === 'string' ? node.character : node.lineText.character;
919
+ const lineText = typeof node.lineText === 'string' ? node.lineText : node.lineText.text;
920
+ let start = Math.max(0, character - 26);
921
+ const wordBreak = /\b/g;
922
+ while (start > 0 && wordBreak.test(lineText) && wordBreak.lastIndex < character) {
923
+ if (character - wordBreak.lastIndex < 26) {
924
+ break;
925
+ }
926
+ start = wordBreak.lastIndex;
927
+ wordBreak.lastIndex++;
928
+ }
929
+ const before = lineText.slice(start, character - 1).trimStart();
930
+ const lineCount = lineText.split('\n').length;
931
+ return React.createElement(React.Fragment, null,
932
+ React.createElement("div", { className: `resultLine noWrapInfo noselect ${node.selected ? 'selected' : ''}`, title: lineText.trim() },
933
+ this.searchInWorkspacePreferences['search.lineNumbers'] && React.createElement("span", { className: 'theia-siw-lineNumber' }, node.line),
934
+ React.createElement("span", null, before),
935
+ this.renderMatchLinePart(node),
936
+ lineCount > 1 || React.createElement("span", null, lineText.slice(node.character + node.length - 1, 250 - before.length + node.length))),
937
+ lineCount > 1 && React.createElement("div", { className: 'match-line-num' },
938
+ "+",
939
+ lineCount - 1));
940
+ }
941
+ renderMatchLinePart(node) {
942
+ const replaceTermLines = this._replaceTerm.split('\n');
943
+ const replaceTerm = this.isReplacing ? React.createElement("span", { className: 'replace-term' }, replaceTermLines[0]) : '';
944
+ const className = `match${this.isReplacing ? ' strike-through' : ''}`;
945
+ const text = typeof node.lineText === 'string' ? node.lineText : node.lineText.text;
946
+ const match = text.substring(node.character - 1, node.character + node.length - 1);
947
+ const matchLines = match.split('\n');
948
+ return React.createElement(React.Fragment, null,
949
+ React.createElement("span", { className: className }, matchLines[0]),
950
+ replaceTerm);
951
+ }
952
+ /**
953
+ * Get the editor widget by the node.
954
+ * @param {SearchInWorkspaceResultLineNode} node - the node representing a match in the search results.
955
+ * @returns The editor widget to which the text replace will be done.
956
+ */
957
+ async doGetWidget(node) {
958
+ const fileUri = new uri_1.default(node.fileUri);
959
+ const editorWidget = await this.editorManager.getOrCreateByUri(fileUri);
960
+ return editorWidget;
961
+ }
962
+ async doOpen(node, asDiffWidget = false, preview = false) {
963
+ let fileUri;
964
+ const resultNode = node.parent;
965
+ if (resultNode && this.isReplacing && asDiffWidget) {
966
+ const leftUri = new uri_1.default(node.fileUri);
967
+ const rightUri = await this.createReplacePreview(resultNode);
968
+ fileUri = browser_1.DiffUris.encode(leftUri, rightUri);
969
+ }
970
+ else {
971
+ fileUri = new uri_1.default(node.fileUri);
972
+ }
973
+ const opts = {
974
+ selection: {
975
+ start: {
976
+ line: node.line - 1,
977
+ character: node.character - 1
978
+ },
979
+ end: this.findEndCharacterPosition(node),
980
+ },
981
+ mode: preview ? 'reveal' : 'activate',
982
+ preview,
983
+ };
984
+ const editorWidget = await this.editorManager.open(fileUri, opts);
985
+ if (!browser_1.DiffUris.isDiffUri(fileUri)) {
986
+ this.decorateEditor(resultNode, editorWidget);
987
+ }
988
+ return editorWidget;
989
+ }
990
+ async createReplacePreview(node) {
991
+ const fileUri = new uri_1.default(node.fileUri).withScheme('file');
992
+ const openedEditor = this.editorManager.all.find(({ editor }) => editor.uri.toString() === fileUri.toString());
993
+ let content;
994
+ if (openedEditor) {
995
+ content = openedEditor.editor.document.getText();
996
+ }
997
+ else {
998
+ const resource = await this.fileResourceResolver.resolve(fileUri);
999
+ content = await resource.readContents();
1000
+ }
1001
+ const searchTermRegExp = new RegExp(this.searchTerm, 'g');
1002
+ return fileUri.withScheme(common_1.MEMORY_TEXT).withQuery(content.replace(searchTermRegExp, this._replaceTerm));
1003
+ }
1004
+ decorateEditor(node, editorWidget) {
1005
+ if (!browser_1.DiffUris.isDiffUri(editorWidget.editor.uri)) {
1006
+ const key = `${editorWidget.editor.uri.toString()}#search-in-workspace-matches`;
1007
+ const oldDecorations = this.appliedDecorations.get(key) || [];
1008
+ const newDecorations = this.createEditorDecorations(node);
1009
+ const appliedDecorations = editorWidget.editor.deltaDecorations({
1010
+ newDecorations,
1011
+ oldDecorations,
1012
+ });
1013
+ this.appliedDecorations.set(key, appliedDecorations);
1014
+ }
1015
+ }
1016
+ createEditorDecorations(resultNode) {
1017
+ const decorations = [];
1018
+ if (resultNode) {
1019
+ resultNode.children.forEach(res => {
1020
+ decorations.push({
1021
+ range: {
1022
+ start: {
1023
+ line: res.line - 1,
1024
+ character: res.character - 1
1025
+ },
1026
+ end: {
1027
+ line: res.line - 1,
1028
+ character: res.character - 1 + res.length
1029
+ }
1030
+ },
1031
+ options: {
1032
+ overviewRuler: {
1033
+ color: {
1034
+ id: 'editor.findMatchHighlightBackground'
1035
+ },
1036
+ position: browser_2.OverviewRulerLane.Center
1037
+ },
1038
+ className: res.selected ? 'current-search-in-workspace-editor-match' : 'search-in-workspace-editor-match',
1039
+ stickiness: browser_2.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore
1040
+ }
1041
+ });
1042
+ });
1043
+ }
1044
+ return decorations;
1045
+ }
1046
+ /**
1047
+ * Get the list of exclude globs.
1048
+ * @param excludeOptions the exclude search option.
1049
+ *
1050
+ * @returns the list of exclude globs.
1051
+ */
1052
+ getExcludeGlobs(excludeOptions) {
1053
+ const excludePreferences = this.filesystemPreferences['files.exclude'];
1054
+ const excludePreferencesGlobs = Object.keys(excludePreferences).filter(key => !!excludePreferences[key]);
1055
+ return [...new Set([...excludePreferencesGlobs, ...excludeOptions || []])];
1056
+ }
1057
+ /**
1058
+ * Compare two normalized strings.
1059
+ *
1060
+ * @param a {string} the first string.
1061
+ * @param b {string} the second string.
1062
+ */
1063
+ compare(a, b) {
1064
+ const itemA = a.toLowerCase().trim();
1065
+ const itemB = b.toLowerCase().trim();
1066
+ return itemA.localeCompare(itemB);
1067
+ }
1068
+ /**
1069
+ * @param recursive if true, all child nodes will be included in the stringified result.
1070
+ */
1071
+ nodeToString(node, recursive) {
1072
+ if (SearchInWorkspaceFileNode.is(node) || SearchInWorkspaceRootFolderNode.is(node)) {
1073
+ if (recursive) {
1074
+ return this.nodeIteratorToString(new browser_1.TopDownTreeIterator(node, { pruneSiblings: true }));
1075
+ }
1076
+ return this.labelProvider.getLongName(node.uri);
1077
+ }
1078
+ if (SearchInWorkspaceResultLineNode.is(node)) {
1079
+ return ` ${node.line}:${node.character}: ${node.lineText}`;
1080
+ }
1081
+ return '';
1082
+ }
1083
+ treeToString() {
1084
+ return this.nodeIteratorToString(this.getVisibleNodes());
1085
+ }
1086
+ *getVisibleNodes() {
1087
+ for (const { node } of this.rows.values()) {
1088
+ yield node;
1089
+ }
1090
+ }
1091
+ nodeIteratorToString(nodes) {
1092
+ const strings = [];
1093
+ for (const node of nodes) {
1094
+ const string = this.nodeToString(node, false);
1095
+ if (string.length !== 0) {
1096
+ strings.push(string);
1097
+ }
1098
+ }
1099
+ return strings.join(core_1.EOL);
1100
+ }
1101
+ };
1102
+ __decorate([
1103
+ (0, inversify_1.inject)(search_in_workspace_service_1.SearchInWorkspaceService),
1104
+ __metadata("design:type", search_in_workspace_service_1.SearchInWorkspaceService)
1105
+ ], SearchInWorkspaceResultTreeWidget.prototype, "searchService", void 0);
1106
+ __decorate([
1107
+ (0, inversify_1.inject)(browser_2.EditorManager),
1108
+ __metadata("design:type", browser_2.EditorManager)
1109
+ ], SearchInWorkspaceResultTreeWidget.prototype, "editorManager", void 0);
1110
+ __decorate([
1111
+ (0, inversify_1.inject)(browser_4.FileResourceResolver),
1112
+ __metadata("design:type", browser_4.FileResourceResolver)
1113
+ ], SearchInWorkspaceResultTreeWidget.prototype, "fileResourceResolver", void 0);
1114
+ __decorate([
1115
+ (0, inversify_1.inject)(browser_1.ApplicationShell),
1116
+ __metadata("design:type", browser_1.ApplicationShell)
1117
+ ], SearchInWorkspaceResultTreeWidget.prototype, "shell", void 0);
1118
+ __decorate([
1119
+ (0, inversify_1.inject)(browser_3.WorkspaceService),
1120
+ __metadata("design:type", browser_3.WorkspaceService)
1121
+ ], SearchInWorkspaceResultTreeWidget.prototype, "workspaceService", void 0);
1122
+ __decorate([
1123
+ (0, inversify_1.inject)(browser_1.TreeExpansionService),
1124
+ __metadata("design:type", Object)
1125
+ ], SearchInWorkspaceResultTreeWidget.prototype, "expansionService", void 0);
1126
+ __decorate([
1127
+ (0, inversify_1.inject)(search_in_workspace_preferences_1.SearchInWorkspacePreferences),
1128
+ __metadata("design:type", Object)
1129
+ ], SearchInWorkspaceResultTreeWidget.prototype, "searchInWorkspacePreferences", void 0);
1130
+ __decorate([
1131
+ (0, inversify_1.inject)(core_1.ProgressService),
1132
+ __metadata("design:type", core_1.ProgressService)
1133
+ ], SearchInWorkspaceResultTreeWidget.prototype, "progressService", void 0);
1134
+ __decorate([
1135
+ (0, inversify_1.inject)(color_registry_1.ColorRegistry),
1136
+ __metadata("design:type", color_registry_1.ColorRegistry)
1137
+ ], SearchInWorkspaceResultTreeWidget.prototype, "colorRegistry", void 0);
1138
+ __decorate([
1139
+ (0, inversify_1.inject)(browser_4.FileSystemPreferences),
1140
+ __metadata("design:type", Object)
1141
+ ], SearchInWorkspaceResultTreeWidget.prototype, "filesystemPreferences", void 0);
1142
+ __decorate([
1143
+ (0, inversify_1.inject)(file_service_1.FileService),
1144
+ __metadata("design:type", file_service_1.FileService)
1145
+ ], SearchInWorkspaceResultTreeWidget.prototype, "fileService", void 0);
1146
+ __decorate([
1147
+ (0, inversify_1.postConstruct)(),
1148
+ __metadata("design:type", Function),
1149
+ __metadata("design:paramtypes", []),
1150
+ __metadata("design:returntype", void 0)
1151
+ ], SearchInWorkspaceResultTreeWidget.prototype, "init", null);
1152
+ SearchInWorkspaceResultTreeWidget = __decorate([
1153
+ (0, inversify_1.injectable)(),
1154
+ __param(0, (0, inversify_1.inject)(browser_1.TreeProps)),
1155
+ __param(1, (0, inversify_1.inject)(browser_1.TreeModel)),
1156
+ __param(2, (0, inversify_1.inject)(browser_1.ContextMenuRenderer)),
1157
+ __metadata("design:paramtypes", [Object, Object, browser_1.ContextMenuRenderer])
1158
+ ], SearchInWorkspaceResultTreeWidget);
1159
+ exports.SearchInWorkspaceResultTreeWidget = SearchInWorkspaceResultTreeWidget;
1160
+ (function (SearchInWorkspaceResultTreeWidget) {
1161
+ let Menus;
1162
+ (function (Menus) {
1163
+ Menus.BASE = ['siw-tree-context-menu'];
1164
+ /** Dismiss command, or others that only affect the widget itself */
1165
+ Menus.INTERNAL = [...Menus.BASE, '1_internal'];
1166
+ /** Copy a stringified representation of content */
1167
+ Menus.COPY = [...Menus.BASE, '2_copy'];
1168
+ /** Commands that lead out of the widget, like revealing a file in the navigator */
1169
+ Menus.EXTERNAL = [...Menus.BASE, '3_external'];
1170
+ })(Menus = SearchInWorkspaceResultTreeWidget.Menus || (SearchInWorkspaceResultTreeWidget.Menus = {}));
1171
+ })(SearchInWorkspaceResultTreeWidget = exports.SearchInWorkspaceResultTreeWidget || (exports.SearchInWorkspaceResultTreeWidget = {}));
1172
+ exports.SearchInWorkspaceResultTreeWidget = SearchInWorkspaceResultTreeWidget;
1100
1173
  //# sourceMappingURL=search-in-workspace-result-tree-widget.js.map