@opensumi/ide-search 2.21.13 → 2.22.0

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 (103) hide show
  1. package/lib/browser/index.d.ts +1 -2
  2. package/lib/browser/index.d.ts.map +1 -1
  3. package/lib/browser/index.js +14 -1
  4. package/lib/browser/index.js.map +1 -1
  5. package/lib/browser/replace.d.ts +3 -2
  6. package/lib/browser/replace.d.ts.map +1 -1
  7. package/lib/browser/replace.js +34 -15
  8. package/lib/browser/replace.js.map +1 -1
  9. package/lib/browser/search-contextkey.d.ts +6 -8
  10. package/lib/browser/search-contextkey.d.ts.map +1 -1
  11. package/lib/browser/search-contextkey.js +11 -13
  12. package/lib/browser/search-contextkey.js.map +1 -1
  13. package/lib/browser/search-preferences.d.ts +1 -1
  14. package/lib/browser/search-preferences.d.ts.map +1 -1
  15. package/lib/browser/search-preferences.js +5 -5
  16. package/lib/browser/search-preferences.js.map +1 -1
  17. package/lib/browser/search.contribution.d.ts +9 -11
  18. package/lib/browser/search.contribution.d.ts.map +1 -1
  19. package/lib/browser/search.contribution.js +126 -80
  20. package/lib/browser/search.contribution.js.map +1 -1
  21. package/lib/browser/search.input.widget.d.ts +3 -5
  22. package/lib/browser/search.input.widget.d.ts.map +1 -1
  23. package/lib/browser/search.input.widget.js +12 -13
  24. package/lib/browser/search.input.widget.js.map +1 -1
  25. package/lib/browser/search.module.less +23 -49
  26. package/lib/browser/search.replace.widget.d.ts +4 -8
  27. package/lib/browser/search.replace.widget.d.ts.map +1 -1
  28. package/lib/browser/search.replace.widget.js +5 -5
  29. package/lib/browser/search.replace.widget.js.map +1 -1
  30. package/lib/browser/search.rules.widget.d.ts.map +1 -1
  31. package/lib/browser/search.rules.widget.js +6 -6
  32. package/lib/browser/search.rules.widget.js.map +1 -1
  33. package/lib/browser/search.service.d.ts +43 -36
  34. package/lib/browser/search.service.d.ts.map +1 -1
  35. package/lib/browser/search.service.js +127 -187
  36. package/lib/browser/search.service.js.map +1 -1
  37. package/lib/browser/search.view.d.ts +14 -3
  38. package/lib/browser/search.view.d.ts.map +1 -1
  39. package/lib/browser/search.view.js +132 -50
  40. package/lib/browser/search.view.js.map +1 -1
  41. package/lib/browser/tree/search-node.d.ts +22 -0
  42. package/lib/browser/tree/search-node.d.ts.map +1 -0
  43. package/lib/browser/tree/search-node.js +155 -0
  44. package/lib/browser/tree/search-node.js.map +1 -0
  45. package/lib/browser/tree/search-tree.service.d.ts +56 -0
  46. package/lib/browser/tree/search-tree.service.d.ts.map +1 -0
  47. package/lib/browser/tree/search-tree.service.js +277 -0
  48. package/lib/browser/tree/search-tree.service.js.map +1 -0
  49. package/lib/browser/tree/search-tree.view.d.ts +21 -0
  50. package/lib/browser/tree/search-tree.view.d.ts.map +1 -0
  51. package/lib/browser/tree/search-tree.view.js +89 -0
  52. package/lib/browser/tree/search-tree.view.js.map +1 -0
  53. package/lib/browser/tree/tree-model.service.d.ts +59 -0
  54. package/lib/browser/tree/tree-model.service.d.ts.map +1 -0
  55. package/lib/browser/tree/tree-model.service.js +292 -0
  56. package/lib/browser/tree/tree-model.service.js.map +1 -0
  57. package/lib/browser/tree/tree-node.defined.d.ts +32 -0
  58. package/lib/browser/tree/tree-node.defined.d.ts.map +1 -0
  59. package/lib/browser/tree/tree-node.defined.js +55 -0
  60. package/lib/browser/tree/tree-node.defined.js.map +1 -0
  61. package/lib/browser/tree/tree-node.module.less +175 -0
  62. package/lib/common/content-search.d.ts +34 -16
  63. package/lib/common/content-search.d.ts.map +1 -1
  64. package/lib/common/content-search.js +2 -6
  65. package/lib/common/content-search.js.map +1 -1
  66. package/lib/node/content-search.service.d.ts +3 -5
  67. package/lib/node/content-search.service.d.ts.map +1 -1
  68. package/lib/node/content-search.service.js +3 -4
  69. package/lib/node/content-search.service.js.map +1 -1
  70. package/lib/node/index.js.map +1 -1
  71. package/package.json +21 -20
  72. package/src/browser/index.ts +42 -0
  73. package/src/browser/replace.ts +119 -0
  74. package/src/browser/search-contextkey.ts +30 -0
  75. package/src/browser/search-history.ts +113 -0
  76. package/src/browser/search-preferences.ts +78 -0
  77. package/src/browser/search-result-collection.ts +75 -0
  78. package/src/browser/search.contribution.ts +347 -0
  79. package/src/browser/search.input.widget.tsx +129 -0
  80. package/src/browser/search.module.less +352 -0
  81. package/src/browser/search.replace.widget.tsx +38 -0
  82. package/src/browser/search.rules.widget.tsx +204 -0
  83. package/src/browser/search.service.ts +881 -0
  84. package/src/browser/search.view.tsx +282 -0
  85. package/src/browser/tree/search-node.tsx +274 -0
  86. package/src/browser/tree/search-tree.service.ts +323 -0
  87. package/src/browser/tree/search-tree.view.tsx +179 -0
  88. package/src/browser/tree/tree-model.service.ts +338 -0
  89. package/src/browser/tree/tree-node.defined.ts +73 -0
  90. package/src/browser/tree/tree-node.module.less +175 -0
  91. package/src/common/content-search.ts +312 -0
  92. package/src/common/index.ts +1 -0
  93. package/src/index.ts +1 -0
  94. package/src/node/content-search.service.ts +297 -0
  95. package/src/node/index.ts +23 -0
  96. package/lib/browser/search-tree.service.d.ts +0 -76
  97. package/lib/browser/search-tree.service.d.ts.map +0 -1
  98. package/lib/browser/search-tree.service.js +0 -587
  99. package/lib/browser/search-tree.service.js.map +0 -1
  100. package/lib/browser/search-tree.view.d.ts +0 -22
  101. package/lib/browser/search-tree.view.d.ts.map +0 -1
  102. package/lib/browser/search-tree.view.js +0 -102
  103. package/lib/browser/search-tree.view.js.map +0 -1
@@ -0,0 +1,881 @@
1
+ import debounce from 'lodash/debounce';
2
+ import { createRef } from 'react';
3
+
4
+ import { Injectable, Autowired } from '@opensumi/di';
5
+ import { VALIDATE_TYPE, ValidateMessage } from '@opensumi/ide-components';
6
+ import { Schemes, CommandService, COMMON_COMMANDS, RecentStorage, PreferenceService } from '@opensumi/ide-core-browser';
7
+ import {
8
+ isUndefined,
9
+ strings,
10
+ parseGlob,
11
+ ParsedPattern,
12
+ Emitter,
13
+ IDisposable,
14
+ URI,
15
+ arrays,
16
+ } from '@opensumi/ide-core-browser';
17
+ import { CorePreferences } from '@opensumi/ide-core-browser/lib/core-preferences';
18
+ import { GlobalBrowserStorageService } from '@opensumi/ide-core-browser/lib/services/storage-service';
19
+ import {
20
+ IEventBus,
21
+ localize,
22
+ IReporterService,
23
+ IReporterTimer,
24
+ REPORT_NAME,
25
+ Disposable,
26
+ CancellationTokenSource,
27
+ CancellationToken,
28
+ } from '@opensumi/ide-core-common';
29
+ import { SearchSettingId } from '@opensumi/ide-core-common/lib/settings/search';
30
+ import { WorkbenchEditorService } from '@opensumi/ide-editor';
31
+ import {
32
+ ICodeEditor,
33
+ IEditorDocumentModelService,
34
+ IEditorDocumentModel,
35
+ EditorDocumentModelContentChangedEvent,
36
+ IEditorDocumentModelContentChangedEventPayload,
37
+ ResourceService,
38
+ } from '@opensumi/ide-editor/lib/browser';
39
+ import { IDialogService, IMessageService } from '@opensumi/ide-overlay';
40
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
41
+ import { IWorkspaceEditService } from '@opensumi/ide-workspace-edit';
42
+ import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';
43
+
44
+ import {
45
+ ContentSearchResult,
46
+ SEARCH_STATE,
47
+ ContentSearchOptions,
48
+ IContentSearchServer,
49
+ ContentSearchServerPath,
50
+ ResultTotal,
51
+ SendClientResult,
52
+ anchorGlob,
53
+ IContentSearchClientService,
54
+ IUIState,
55
+ cutShortSearchResult,
56
+ FilterFileWithGlobRelativePath,
57
+ DEFAULT_SEARCH_IN_WORKSPACE_LIMIT,
58
+ } from '../common';
59
+
60
+ import { replaceAll } from './replace';
61
+ import { SearchHistory } from './search-history';
62
+ import { SearchPreferences } from './search-preferences';
63
+ import { SearchResultCollection } from './search-result-collection';
64
+
65
+ export interface SearchAllFromDocModelOptions {
66
+ searchValue: string;
67
+ searchOptions: ContentSearchOptions;
68
+ documentModelManager: IEditorDocumentModelService;
69
+ rootDirs: string[];
70
+ }
71
+
72
+ function splitOnComma(patterns: string): string[] {
73
+ return patterns.length > 0 ? patterns.split(',').map((s) => s.trim()) : [];
74
+ }
75
+
76
+ /**
77
+ * 用于文件内容搜索
78
+ */
79
+ @Injectable()
80
+ export class ContentSearchClientService extends Disposable implements IContentSearchClientService {
81
+ @Autowired(IEventBus)
82
+ private readonly eventBus: IEventBus;
83
+
84
+ @Autowired(SearchPreferences)
85
+ private readonly searchPreferences: SearchPreferences;
86
+
87
+ @Autowired(CorePreferences)
88
+ private readonly corePreferences: CorePreferences;
89
+
90
+ @Autowired(ContentSearchServerPath)
91
+ private readonly contentSearchServer: IContentSearchServer;
92
+
93
+ @Autowired(IWorkspaceService)
94
+ private readonly workspaceService: IWorkspaceService;
95
+
96
+ @Autowired(RecentStorage)
97
+ private readonly recentStorage: RecentStorage;
98
+
99
+ @Autowired(IEditorDocumentModelService)
100
+ private readonly documentModelManager: IEditorDocumentModelService;
101
+
102
+ @Autowired(CommandService)
103
+ private readonly commandService: CommandService;
104
+
105
+ @Autowired(GlobalBrowserStorageService)
106
+ private readonly browserStorageService: GlobalBrowserStorageService;
107
+
108
+ @Autowired(IDialogService)
109
+ private readonly dialogService;
110
+
111
+ @Autowired(IMessageService)
112
+ private readonly messageService;
113
+
114
+ @Autowired(IWorkspaceEditService)
115
+ private readonly workspaceEditService: IWorkspaceEditService;
116
+
117
+ @Autowired(IReporterService)
118
+ private reporterService: IReporterService;
119
+
120
+ @Autowired(PreferenceService)
121
+ private readonly preferenceService: PreferenceService;
122
+
123
+ @Autowired(WorkbenchEditorService)
124
+ private readonly workbenchEditorService: WorkbenchEditorService;
125
+
126
+ @Autowired()
127
+ private readonly resourceService: ResourceService;
128
+
129
+ private onDidChangeEmitter: Emitter<void> = new Emitter();
130
+ private onDidTitleChangeEmitter: Emitter<void> = new Emitter();
131
+ private onDidUIStateChangeEmitter: Emitter<IUIState> = new Emitter();
132
+ private onDidSearchStateChangeEmitter: Emitter<string> = new Emitter();
133
+
134
+ protected eventBusDisposer: IDisposable;
135
+
136
+ get onDidChange() {
137
+ return this.onDidChangeEmitter.event;
138
+ }
139
+
140
+ get onDidTitleChange() {
141
+ return this.onDidTitleChangeEmitter.event;
142
+ }
143
+
144
+ get onDidUIStateChange() {
145
+ return this.onDidUIStateChangeEmitter.event;
146
+ }
147
+
148
+ get onDidSearchStateChange() {
149
+ return this.onDidSearchStateChangeEmitter.event;
150
+ }
151
+
152
+ public UIState: IUIState = {
153
+ isToggleOpen: true,
154
+ isDetailOpen: false,
155
+ // Search Options
156
+ isMatchCase: false,
157
+ isWholeWord: false,
158
+ isUseRegexp: false,
159
+ isIncludeIgnored: false,
160
+ isOnlyOpenEditors: false,
161
+ };
162
+
163
+ public searchResults: Map<string, ContentSearchResult[]> = new Map();
164
+ public searchError = '';
165
+ public searchState: SEARCH_STATE;
166
+ public resultTotal: ResultTotal = { resultNum: 0, fileNum: 0 };
167
+ public isReplacing = false;
168
+ public isSearching = false;
169
+ public isShowValidateMessage = true;
170
+ public replaceValue = '';
171
+ public searchValue = '';
172
+ public includeValue = '';
173
+ public excludeValue = '';
174
+
175
+ private _searchHistory: SearchHistory;
176
+ private _docModelSearchedList: string[] = [];
177
+ private _currentSearchId = -1;
178
+
179
+ searchResultCollection: SearchResultCollection = new SearchResultCollection();
180
+
181
+ private reporter: { timer: IReporterTimer; value: string } | null = null;
182
+
183
+ private searchCancelToken: CancellationTokenSource;
184
+ private searchOnType: boolean;
185
+
186
+ public searchDebounce: () => void;
187
+
188
+ constructor() {
189
+ super();
190
+ this.setDefaultIncludeValue();
191
+ this.recoverUIState();
192
+
193
+ this.searchOnType = this.searchPreferences[SearchSettingId.SearchOnType] || true;
194
+ const timeout = this.searchPreferences[SearchSettingId.SearchOnTypeDebouncePeriod] || 300;
195
+ this.searchDebounce = debounce(
196
+ () => {
197
+ this.search();
198
+ },
199
+ timeout,
200
+ {
201
+ trailing: true,
202
+ maxWait: timeout * 5,
203
+ },
204
+ );
205
+ }
206
+
207
+ private searchId: number = new Date().getTime();
208
+
209
+ public searchOnTyping() {
210
+ if (this.searchOnType) {
211
+ this.searchDebounce();
212
+ }
213
+ }
214
+
215
+ async search(insertUIState?: IUIState) {
216
+ const value = this.searchValue;
217
+ this.cleanSearchResults();
218
+ if (!value) {
219
+ this.onDidChangeEmitter.fire();
220
+ return;
221
+ }
222
+ if (this.searchCancelToken && !this.searchCancelToken.token.isCancellationRequested) {
223
+ this.searchCancelToken.cancel();
224
+ }
225
+
226
+ this.searchCancelToken = new CancellationTokenSource();
227
+
228
+ const state = insertUIState || this.UIState;
229
+
230
+ await this.doSearch(value, state, this.searchCancelToken.token);
231
+ }
232
+
233
+ async doSearch(value: string, state: IUIState, token: CancellationToken) {
234
+ const searchOptions: ContentSearchOptions = {
235
+ maxResults: 2000,
236
+ matchCase: state.isMatchCase,
237
+ matchWholeWord: state.isWholeWord,
238
+ useRegExp: state.isUseRegexp,
239
+ includeIgnored: state.isIncludeIgnored,
240
+
241
+ include: splitOnComma(this.includeValue || ''),
242
+ exclude: splitOnComma(this.excludeValue || ''),
243
+ };
244
+
245
+ searchOptions.exclude = this.getExcludeWithSetting(searchOptions, state);
246
+
247
+ // 记录搜索历史
248
+ this.searchHistory.setSearchHistory(value);
249
+
250
+ this.isShowValidateMessage = true;
251
+
252
+ // Stop old search
253
+ this.isSearching = true;
254
+ if (this._currentSearchId > -1) {
255
+ this.contentSearchServer.cancel(this._currentSearchId);
256
+ this._currentSearchId = this._currentSearchId + 1;
257
+ this.reporter = null;
258
+ }
259
+
260
+ const rootDirSet = new Set<string>();
261
+ this.workspaceService.tryGetRoots().forEach((stat) => {
262
+ const uri = new URI(stat.uri);
263
+ if (uri.scheme !== Schemes.file) {
264
+ return;
265
+ }
266
+ rootDirSet.add(uri.toString());
267
+ });
268
+
269
+ // 由于查询的限制,暂时只支持单一 workspace 的编码参数
270
+ searchOptions.encoding = this.preferenceService.get<string>(
271
+ 'files.encoding',
272
+ undefined,
273
+ rootDirSet.values().next()?.value,
274
+ );
275
+
276
+ // FIXME: 当前无法在不同根目录内根据各自 include 搜索,因此如果多 workspaceFolders,此处返回的结果仅为一部分
277
+ // 同时 searchId 设计原因只能针对单服务,多个 search 服务无法对同一个 searchId 返回结果
278
+ // 长期看需要改造,以支持 registerFileSearchProvider
279
+ if (state.isOnlyOpenEditors) {
280
+ rootDirSet.clear();
281
+ const openResources = arrays.coalesce(
282
+ arrays.flatten(this.workbenchEditorService.editorGroups.map((group) => group.resources)),
283
+ );
284
+ const includeMatcherList = searchOptions.include?.map((str) => parseGlob(anchorGlob(str))) || [];
285
+ const excludeMatcherList = searchOptions.exclude?.map((str) => parseGlob(anchorGlob(str))) || [];
286
+ const openResourcesInFilter = openResources.filter((resource) => {
287
+ const fsPath = resource.uri.path.toString();
288
+ if (excludeMatcherList.length > 0 && excludeMatcherList.some((matcher) => matcher(fsPath))) {
289
+ return false;
290
+ }
291
+ if (includeMatcherList.length > 0 && !includeMatcherList.some((matcher) => matcher(fsPath))) {
292
+ return false;
293
+ }
294
+ return true;
295
+ });
296
+ const include: string[] = [];
297
+ const isAbsolutePath = (resource: URI): boolean => !!resource.codeUri.path && resource.codeUri.path[0] === '/';
298
+ openResourcesInFilter.forEach(({ uri }) => {
299
+ if (uri.scheme === Schemes.walkThrough) {
300
+ return;
301
+ }
302
+ if (isAbsolutePath(uri)) {
303
+ const searchRoot = this.workspaceService.getWorkspaceRootUri(uri) ?? uri.withPath(uri.path.dir);
304
+ const relPath = searchRoot.path.relative(uri.path);
305
+ rootDirSet.add(searchRoot.toString());
306
+ if (relPath) {
307
+ include.push(`./${relPath.toString()}`);
308
+ }
309
+ } else if (uri.codeUri.fsPath) {
310
+ include.push(uri.codeUri.fsPath);
311
+ }
312
+ });
313
+
314
+ searchOptions.include = include;
315
+ searchOptions.exclude = include.length > 0 ? undefined : ['**/*'];
316
+ }
317
+ const rootDirs = Array.from(rootDirSet);
318
+ // 从 doc model 中搜索
319
+ const searchFromDocModelInfo = await this.searchAllFromDocModel({
320
+ searchValue: value,
321
+ searchOptions,
322
+ documentModelManager: this.documentModelManager,
323
+ rootDirs,
324
+ });
325
+ if (token.isCancellationRequested) {
326
+ this.isSearching = false;
327
+ return;
328
+ }
329
+ this._currentSearchId = this.searchId++;
330
+
331
+ // 从服务端搜索
332
+ this.reporter = { timer: this.reporterService.time(REPORT_NAME.SEARCH_MEASURE), value };
333
+ this.contentSearchServer.search(this._currentSearchId, value, rootDirs, searchOptions).then((id) => {
334
+ if (token.isCancellationRequested) {
335
+ return;
336
+ }
337
+ this._onSearchResult({
338
+ id,
339
+ data: searchFromDocModelInfo.result,
340
+ searchState: SEARCH_STATE.doing,
341
+ docModelSearchedList: searchFromDocModelInfo.searchedList,
342
+ });
343
+ });
344
+
345
+ this.watchDocModelContentChange(searchOptions, rootDirs);
346
+ }
347
+
348
+ // #region 操作对当前打开的文档的搜索内容的 selection
349
+ private EMPTY_SELECTION = new monaco.Range(0, 0, 0, 0);
350
+ private lastEditor?: ICodeEditor;
351
+ private lastSelection?: monaco.Range;
352
+ setEditorSelections(editor: ICodeEditor, selections: monaco.Range) {
353
+ // 清除上一个 editor 的 selection
354
+ this.lastEditor?.setSelection(this.EMPTY_SELECTION);
355
+
356
+ this.lastEditor = editor;
357
+ this.lastSelection = selections;
358
+ this.applyEditorSelections();
359
+ }
360
+ /**
361
+ * 会在 tabbar 被选中时调用
362
+ */
363
+ applyEditorSelections() {
364
+ if (this.lastEditor && this.lastSelection) {
365
+ this.lastEditor.setSelection(this.lastSelection);
366
+ }
367
+ }
368
+ /**
369
+ * 会在 tabbar blur 和清除搜索结果时调用
370
+ * @param clearEditor 是否清除上次选中的 editor(在重置搜索内容时调用)
371
+ */
372
+ clearEditorSelections(clearEditor = false) {
373
+ if (this.lastEditor) {
374
+ this.lastEditor.setSelection(this.EMPTY_SELECTION);
375
+ }
376
+ if (clearEditor) {
377
+ this.lastSelection = undefined;
378
+ this.lastEditor = undefined;
379
+ }
380
+ }
381
+ // #endregion
382
+
383
+ /**
384
+ * 监听正在编辑文件变化,并同步结果
385
+ * @param searchOptions
386
+ * @param rootDirs
387
+ */
388
+ watchDocModelContentChange(searchOptions: ContentSearchOptions, rootDirs: string[]) {
389
+ if (this.eventBusDisposer) {
390
+ this.eventBusDisposer.dispose();
391
+ }
392
+ this.eventBusDisposer = this.eventBus.on(EditorDocumentModelContentChangedEvent, (data) => {
393
+ const event: IEditorDocumentModelContentChangedEventPayload = data.payload;
394
+
395
+ if (!this.searchResults || this.isReplacing) {
396
+ return;
397
+ }
398
+
399
+ // 只搜索file协议内容
400
+ if (event.uri.scheme !== Schemes.file) {
401
+ return;
402
+ }
403
+
404
+ const uriString = event.uri.toString();
405
+
406
+ const docModel = this.documentModelManager.getModelReference(event.uri);
407
+ if (!docModel) {
408
+ return;
409
+ }
410
+ const resultData = this.searchFromDocModel(searchOptions, docModel.instance, this.searchValue, rootDirs);
411
+
412
+ const oldResults = this.searchResults.get(uriString);
413
+
414
+ if (!oldResults) {
415
+ // 不在结果树中,新增结果
416
+ if (resultData.result.length > 0) {
417
+ this.resultTotal.fileNum++;
418
+ this.resultTotal.resultNum = this.resultTotal.resultNum + resultData.result.length;
419
+ }
420
+ } else if (resultData.result.length < 1) {
421
+ // 搜索结果被删除完了,清理结果树
422
+ this.searchResults.delete(uriString);
423
+ this.resultTotal.fileNum = this.resultTotal.fileNum - 1;
424
+ this.resultTotal.resultNum = this.resultTotal.resultNum - oldResults.length;
425
+ return;
426
+ } else if (resultData.result.length !== oldResults?.length) {
427
+ // 搜索结果变多了,更新数据
428
+ this.resultTotal.resultNum = this.resultTotal.resultNum - oldResults!.length + resultData.result.length;
429
+ }
430
+ if (resultData.result.length > 0) {
431
+ // 更新结果树
432
+ this.searchResults.set(uriString, resultData.result);
433
+ }
434
+ this.onDidChangeEmitter.fire();
435
+ docModel.dispose();
436
+ });
437
+ }
438
+
439
+ cleanSearchResults() {
440
+ this._docModelSearchedList = [];
441
+ this.searchResults.clear();
442
+ this.resultTotal = { resultNum: 0, fileNum: 0 };
443
+ this.clearEditorSelections(true);
444
+ }
445
+
446
+ /**
447
+ * 服务端发送搜索结果过来
448
+ * @param sendClientResult
449
+ */
450
+ onSearchResult(sendClientResult: SendClientResult) {
451
+ const resultList = this.searchResultCollection.pushAndGetResultList(sendClientResult);
452
+ resultList.forEach((result) => {
453
+ this._onSearchResult(result);
454
+ });
455
+ }
456
+
457
+ private _onSearchResult(sendClientResult: SendClientResult) {
458
+ const { id, data, searchState, error, docModelSearchedList } = sendClientResult;
459
+
460
+ if (!data) {
461
+ return;
462
+ }
463
+
464
+ if (id > this._currentSearchId) {
465
+ this.isSearching = true;
466
+ this._currentSearchId = id;
467
+ this.cleanSearchResults();
468
+ }
469
+
470
+ if (this._currentSearchId && this._currentSearchId > id) {
471
+ // 若存在异步发送的上次搜索结果,丢弃上次搜索的结果
472
+ return;
473
+ }
474
+
475
+ if (searchState) {
476
+ this.searchState = searchState;
477
+ if (searchState === SEARCH_STATE.done || searchState === SEARCH_STATE.error) {
478
+ // 搜索结束清理ID
479
+ this.isSearching = false;
480
+ this._currentSearchId = -1;
481
+ }
482
+
483
+ if (searchState === SEARCH_STATE.done && this.reporter) {
484
+ const { timer, value } = this.reporter;
485
+ timer.timeEnd(value, {
486
+ ...this.resultTotal,
487
+ });
488
+ this.reporter = null;
489
+ }
490
+ }
491
+
492
+ if (error) {
493
+ // 搜索出错
494
+ this.isSearching = false;
495
+ this.searchError = error.toString();
496
+ this.reporter = null;
497
+ }
498
+
499
+ this.mergeSameUriResult(data, this.searchResults, this._docModelSearchedList, this.resultTotal);
500
+
501
+ if (docModelSearchedList) {
502
+ // 记录通 docModel 搜索过的文件,用于过滤服务端搜索的重复内容
503
+ this._docModelSearchedList = docModelSearchedList;
504
+ }
505
+ this.onDidChangeEmitter.fire();
506
+ }
507
+
508
+ focus() {
509
+ window.requestAnimationFrame(() => {
510
+ this.onDidSearchStateChangeEmitter.fire(this.searchValue);
511
+ this.applyEditorSelections();
512
+ });
513
+ }
514
+
515
+ blur() {
516
+ this.clearEditorSelections();
517
+ }
518
+
519
+ refreshIsEnable() {
520
+ return !!(this.searchState !== SEARCH_STATE.doing && this.searchValue);
521
+ }
522
+
523
+ initSearchHistory() {
524
+ return this.searchHistory.initSearchHistory();
525
+ }
526
+
527
+ setBackRecentSearchWord() {
528
+ return this.searchHistory.setBackRecentSearchWord();
529
+ }
530
+
531
+ setRecentSearchWord() {
532
+ return this.searchHistory.setRecentSearchWord();
533
+ }
534
+
535
+ clean() {
536
+ this.searchResults.clear();
537
+ this.resultTotal = { resultNum: 0, fileNum: 0 };
538
+ this.searchState = SEARCH_STATE.todo;
539
+ this.searchValue = '';
540
+ this.replaceValue = '';
541
+ this.excludeValue = '';
542
+ this.includeValue = '';
543
+ this.searchError = '';
544
+ }
545
+
546
+ cleanIsEnable() {
547
+ return !!(
548
+ this.searchValue ||
549
+ this.replaceValue ||
550
+ this.excludeValue ||
551
+ this.includeValue ||
552
+ this.searchError ||
553
+ (this.searchResults && this.searchResults.size > 0)
554
+ );
555
+ }
556
+
557
+ foldIsEnable() {
558
+ return !!(this.searchResults && this.searchResults.size > 0);
559
+ }
560
+
561
+ onSearchInputChange = (text: string) => {
562
+ this.searchValue = text;
563
+ this.isShowValidateMessage = false;
564
+ this.searchOnTyping();
565
+ };
566
+
567
+ onReplaceInputChange = (text: string) => {
568
+ this.replaceValue = text;
569
+ };
570
+
571
+ onSearchExcludeChange = (text: string) => {
572
+ this.excludeValue = text;
573
+ this.searchOnTyping();
574
+ };
575
+
576
+ onSearchIncludeChange = (text: string) => {
577
+ this.includeValue = text;
578
+ this.searchOnTyping();
579
+ };
580
+
581
+ searchEditorSelection = () => {
582
+ const currentEditor = this.workbenchEditorService.currentEditor;
583
+ if (currentEditor) {
584
+ const selections = currentEditor.getSelections();
585
+ if (selections && selections.length > 0 && currentEditor.currentDocumentModel) {
586
+ const { selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn } = selections[0];
587
+ const selectionText = currentEditor.currentDocumentModel.getText(
588
+ new monaco.Range(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn),
589
+ );
590
+
591
+ const searchText = strings.trim(selectionText) === '' ? this.searchValue : selectionText;
592
+ this.searchValue = searchText;
593
+ }
594
+ }
595
+ };
596
+
597
+ private shouldSearch = (uiState: Partial<typeof this.UIState>) =>
598
+ ['isWholeWord', 'isMatchCase', 'isUseRegexp', 'isIncludeIgnored', 'isOnlyOpenEditors'].some(
599
+ (v) => uiState[v] !== undefined && uiState[v] !== this.UIState[v],
600
+ );
601
+
602
+ updateUIState = (obj: Partial<typeof this.UIState>) => {
603
+ const newUIState = Object.assign({}, this.UIState, obj);
604
+
605
+ if (this.shouldSearch(obj)) {
606
+ this.search(newUIState);
607
+ }
608
+
609
+ this.UIState = newUIState;
610
+ this.onDidUIStateChangeEmitter.fire(newUIState);
611
+ this.browserStorageService.setData('search.UIState', newUIState);
612
+ };
613
+
614
+ getPreferenceSearchExcludes(): string[] {
615
+ const excludes: string[] = [];
616
+ const fileExcludes = this.corePreferences['files.exclude'];
617
+ const searchExcludes = this.searchPreferences['search.exclude'];
618
+ const allExcludes = Object.assign({}, fileExcludes, searchExcludes);
619
+ for (const key of Object.keys(allExcludes)) {
620
+ if (allExcludes[key]) {
621
+ excludes.push(key);
622
+ }
623
+ }
624
+ return excludes;
625
+ }
626
+
627
+ openPreference() {
628
+ this.commandService.executeCommand(COMMON_COMMANDS.OPEN_PREFERENCES.id, 'files.watcherExclude');
629
+ }
630
+
631
+ get searchHistory(): SearchHistory {
632
+ if (!this._searchHistory) {
633
+ this._searchHistory = new SearchHistory(this, this.recentStorage);
634
+ }
635
+ return this._searchHistory;
636
+ }
637
+
638
+ get validateMessage(): ValidateMessage | undefined {
639
+ if (this.resultTotal.resultNum >= DEFAULT_SEARCH_IN_WORKSPACE_LIMIT) {
640
+ return {
641
+ message: localize('search.too.many.results'),
642
+ type: VALIDATE_TYPE.WARNING,
643
+ };
644
+ }
645
+ }
646
+
647
+ replaceAll = () => {
648
+ if (this.isReplacing) {
649
+ return;
650
+ }
651
+ this.isReplacing = true;
652
+ replaceAll(
653
+ this.documentModelManager,
654
+ this.workspaceEditService,
655
+ this.searchResults,
656
+ this.replaceValue,
657
+ this.searchValue,
658
+ this.UIState.isUseRegexp,
659
+ this.dialogService,
660
+ this.messageService,
661
+ this.resultTotal,
662
+ ).then((isDone) => {
663
+ this.isReplacing = false;
664
+ if (!isDone) {
665
+ return;
666
+ }
667
+ this.search();
668
+ });
669
+ };
670
+
671
+ private async recoverUIState() {
672
+ const UIState = (await this.browserStorageService.getData('search.UIState')) as IUIState | undefined;
673
+ this.updateUIState(UIState || {});
674
+ }
675
+
676
+ private getExcludeWithSetting(searchOptions: ContentSearchOptions, state: IUIState) {
677
+ let result: string[] = [];
678
+
679
+ if (searchOptions.exclude) {
680
+ result = result.concat(searchOptions.exclude);
681
+ }
682
+
683
+ // 启用默认排除项
684
+ if (!state.isIncludeIgnored) {
685
+ result = result.concat(this.getPreferenceSearchExcludes());
686
+ }
687
+
688
+ return result;
689
+ }
690
+
691
+ private setDefaultIncludeValue() {
692
+ const searchIncludes = this.searchPreferences[SearchSettingId.Include] || {};
693
+ this.includeValue = Object.keys(searchIncludes)
694
+ .reduce<string[]>((includes, key) => {
695
+ if (searchIncludes[key]) {
696
+ includes.push(key);
697
+ }
698
+ return includes;
699
+ }, [])
700
+ .join(',');
701
+ // 如有 include 填充,则显示搜索条件
702
+ if (this.includeValue) {
703
+ this.updateUIState({ isDetailOpen: true });
704
+ }
705
+ }
706
+
707
+ private mergeSameUriResult(
708
+ data: ContentSearchResult[],
709
+ searchResultMap: Map<string, ContentSearchResult[]>,
710
+ docSearchedList: string[],
711
+ total?: ResultTotal,
712
+ ) {
713
+ const theTotal = total || { fileNum: 0, resultNum: 0 };
714
+ data.forEach((result: ContentSearchResult) => {
715
+ const oldData: ContentSearchResult[] | undefined = searchResultMap.get(result.fileUri);
716
+ if (docSearchedList.indexOf(result.fileUri) > -1) {
717
+ // 通过docModel搜索过的文件不再搜索
718
+ return;
719
+ }
720
+ if (oldData) {
721
+ oldData.push(result);
722
+ searchResultMap.set(result.fileUri, oldData);
723
+ theTotal.resultNum++;
724
+ } else {
725
+ searchResultMap.set(result.fileUri, [result]);
726
+ theTotal.fileNum++;
727
+ theTotal.resultNum++;
728
+ }
729
+ });
730
+
731
+ return {
732
+ searchResultMap,
733
+ total: theTotal,
734
+ };
735
+ }
736
+
737
+ private searchFromDocModel(
738
+ searchOptions: ContentSearchOptions,
739
+ docModel: IEditorDocumentModel,
740
+ searchValue: string,
741
+ rootDirs: string[],
742
+ codeEditor?: ICodeEditor,
743
+ ): {
744
+ result: ContentSearchResult[];
745
+ searchedList: string[];
746
+ } {
747
+ let matcherList: ParsedPattern[] = [];
748
+ const uriString = docModel.uri.toString();
749
+
750
+ const result: ContentSearchResult[] = [];
751
+ const searchedList: string[] = [];
752
+ if (!rootDirs.some((root) => uriString.startsWith(root))) {
753
+ return {
754
+ result,
755
+ searchedList,
756
+ };
757
+ }
758
+ if (searchOptions.include && searchOptions.include.length > 0) {
759
+ // include 设置时,若匹配不到则返回空
760
+ searchOptions.include.forEach((str: string) => {
761
+ matcherList.push(parseGlob(anchorGlob(str)));
762
+ });
763
+ const isInclude = matcherList.some((matcher) => matcher(uriString));
764
+ matcherList = [];
765
+ if (!isInclude) {
766
+ return {
767
+ result,
768
+ searchedList,
769
+ };
770
+ }
771
+ }
772
+
773
+ if (searchOptions.exclude && searchOptions.exclude.length > 0) {
774
+ // exclude 设置时,若匹配到则返回空
775
+ searchOptions.exclude.forEach((str: string) => {
776
+ matcherList.push(parseGlob(anchorGlob(str)));
777
+ });
778
+
779
+ const isExclude = matcherList.some((matcher) => matcher(uriString));
780
+ matcherList = [];
781
+ if (isExclude) {
782
+ return {
783
+ result,
784
+ searchedList,
785
+ };
786
+ }
787
+ }
788
+
789
+ const textModel = docModel.getMonacoModel();
790
+ searchedList.push(docModel.uri.toString());
791
+ const findResults = textModel.findMatches(
792
+ searchValue,
793
+ true,
794
+ !!searchOptions.useRegExp,
795
+ !!searchOptions.matchCase,
796
+ searchOptions.matchWholeWord ? '`~!@#$%^&*()-=+[{]}\\|;:\'",.<>/? \n' : null,
797
+ false,
798
+ );
799
+ findResults.forEach((find: monaco.editor.FindMatch, index) => {
800
+ if (index === 0 && codeEditor) {
801
+ // 给打开文件添加选中状态
802
+ this.setEditorSelections(codeEditor, find.range);
803
+ }
804
+ result.push(
805
+ cutShortSearchResult({
806
+ fileUri: docModel.uri.toString(),
807
+ line: find.range.startLineNumber,
808
+ matchStart: find.range.startColumn,
809
+ matchLength: find.range.endColumn - find.range.startColumn,
810
+ lineText: textModel.getLineContent(find.range.startLineNumber),
811
+ }),
812
+ );
813
+ });
814
+
815
+ return {
816
+ result,
817
+ searchedList,
818
+ };
819
+ }
820
+
821
+ private async searchAllFromDocModel(options: SearchAllFromDocModelOptions): Promise<{
822
+ result: ContentSearchResult[];
823
+ searchedList: string[];
824
+ }> {
825
+ const searchValue = options.searchValue;
826
+ const searchOptions = options.searchOptions;
827
+ const documentModelManager = options.documentModelManager;
828
+ const rootDirs = options.rootDirs;
829
+
830
+ let result: ContentSearchResult[] = [];
831
+ let searchedList: string[] = [];
832
+ const docModels = documentModelManager.getAllModels();
833
+ const group = this.workbenchEditorService.currentEditorGroup;
834
+
835
+ const filterFileWithGlobRelativePath = new FilterFileWithGlobRelativePath(rootDirs, searchOptions.include || []);
836
+
837
+ await Promise.all(
838
+ docModels.map(async (docModel: IEditorDocumentModel) => {
839
+ const uriString = docModel.uri.toString();
840
+
841
+ // 只搜索file协议内容
842
+ if (docModel.uri.scheme !== Schemes.file) {
843
+ return;
844
+ }
845
+
846
+ const resource = await this.resourceService.getResource(docModel.uri);
847
+ if (!resource || resource.deleted) {
848
+ return;
849
+ }
850
+
851
+ if (!filterFileWithGlobRelativePath.test(uriString)) {
852
+ return;
853
+ }
854
+
855
+ const data = this.searchFromDocModel(searchOptions, docModel, searchValue, rootDirs, group.codeEditor);
856
+
857
+ result = result.concat(data.result);
858
+ searchedList = searchedList.concat(data.searchedList);
859
+ }),
860
+ );
861
+
862
+ return {
863
+ result,
864
+ searchedList,
865
+ };
866
+ }
867
+
868
+ fireTitleChange() {
869
+ this.onDidTitleChangeEmitter.fire();
870
+ }
871
+
872
+ refresh() {
873
+ this.search();
874
+ }
875
+
876
+ dispose() {
877
+ super.dispose();
878
+ this.eventBusDisposer.dispose();
879
+ this.blur();
880
+ }
881
+ }