@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,323 @@
1
+ import { Injectable, Autowired } from '@opensumi/di';
2
+ import { LabelService } from '@opensumi/ide-core-browser';
3
+ import {
4
+ URI,
5
+ Schemes,
6
+ Emitter,
7
+ IDisposable,
8
+ DisposableStore,
9
+ IRange,
10
+ memoize,
11
+ Disposable,
12
+ } from '@opensumi/ide-core-common';
13
+ import {
14
+ IEditorDocumentModelService,
15
+ IEditorDocumentModelContentRegistry,
16
+ IEditorDocumentModelContentProvider,
17
+ IEditorDocumentModelRef,
18
+ } from '@opensumi/ide-editor/lib/browser';
19
+ import { IFileServiceClient } from '@opensumi/ide-file-service/lib/common';
20
+ import type { IModelDeltaDecoration } from '@opensumi/ide-monaco/lib/browser/monaco-api/editor';
21
+ import type { ITextModel } from '@opensumi/ide-monaco/lib/browser/monaco-api/types';
22
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
23
+ import { IWorkspaceEditService } from '@opensumi/ide-workspace-edit';
24
+ import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';
25
+
26
+ import { IContentSearchClientService, ISearchTreeService } from '../../common/content-search';
27
+ import { replace } from '../replace';
28
+ import { SearchContextKey } from '../search-contextkey';
29
+ import { SearchContentNode, SearchFileNode, SearchRoot } from '../tree/tree-node.defined';
30
+
31
+ @Injectable()
32
+ export class RangeHighlightDecorations implements IDisposable {
33
+ private preDeltaDecoration: string[] | null = null;
34
+ private _model: ITextModel | null = null;
35
+ private _modelRef: IEditorDocumentModelRef | null = null;
36
+ private readonly _modelDisposables = new DisposableStore();
37
+
38
+ @Autowired(IEditorDocumentModelService)
39
+ documentModelManager: IEditorDocumentModelService;
40
+
41
+ removeHighlightRange() {
42
+ if (this._model && this.preDeltaDecoration) {
43
+ this._model.deltaDecorations(this.preDeltaDecoration, []);
44
+ }
45
+ this.preDeltaDecoration = null;
46
+ }
47
+
48
+ highlightRange(resource: URI | ITextModel, range: IRange): void {
49
+ let model: ITextModel | null = null;
50
+ if (URI.isUri(resource)) {
51
+ const modelRef = this.documentModelManager.getModelReference(resource, 'highlight-range');
52
+ if (modelRef) {
53
+ model = modelRef.instance.getMonacoModel();
54
+ this._modelRef = modelRef;
55
+ }
56
+ } else {
57
+ model = resource;
58
+ }
59
+
60
+ if (model) {
61
+ this.doHighlightRange(model, range);
62
+ }
63
+ }
64
+
65
+ private doHighlightRange(model: ITextModel, range: IRange) {
66
+ this.removeHighlightRange();
67
+ this.preDeltaDecoration = model.deltaDecorations([], this.createEditorDecorations(range));
68
+ this.setModel(model);
69
+ }
70
+
71
+ private createEditorDecorations(range: IRange) {
72
+ return [{ range, options: RangeHighlightDecorations.RANGE_HIGHLIGHT_DECORATION }] as IModelDeltaDecoration[];
73
+ }
74
+
75
+ private setModel(model: ITextModel) {
76
+ if (this._modelRef) {
77
+ this._modelRef.dispose();
78
+ this._modelRef = null;
79
+ }
80
+ if (this._model !== model) {
81
+ this.clearModelListeners();
82
+ this._model = model;
83
+
84
+ this._modelDisposables.add(
85
+ this._model.onWillDispose(() => {
86
+ this.clearModelListeners();
87
+ this.removeHighlightRange();
88
+ this._model = null;
89
+ }),
90
+ );
91
+ }
92
+ }
93
+
94
+ private clearModelListeners() {
95
+ this._modelDisposables.clear();
96
+ }
97
+
98
+ dispose() {
99
+ if (this._model) {
100
+ this.removeHighlightRange();
101
+ this._modelDisposables.dispose();
102
+ this._model = null;
103
+ }
104
+ }
105
+
106
+ private static readonly RANGE_HIGHLIGHT_DECORATION = {
107
+ className: 'rangeHighlight',
108
+ stickiness: monaco.editor.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore,
109
+ isWholeLine: true,
110
+ };
111
+ }
112
+
113
+ @Injectable()
114
+ export class ReplaceDocumentModelContentProvider implements IEditorDocumentModelContentProvider {
115
+ contentMap: Map<string, string> = new Map();
116
+
117
+ @Autowired(IEditorDocumentModelService)
118
+ private documentModelManager: IEditorDocumentModelService;
119
+
120
+ @Autowired(IContentSearchClientService)
121
+ private searchBrowserService: IContentSearchClientService;
122
+
123
+ @Autowired(IWorkspaceEditService)
124
+ private workspaceEditService: IWorkspaceEditService;
125
+
126
+ private onDidChangeContentEvent: Emitter<URI> = new Emitter();
127
+
128
+ handlesScheme(scheme: string) {
129
+ return scheme === Schemes.internal;
130
+ }
131
+
132
+ provideEditorDocumentModelContent(uri: URI): string {
133
+ return this.contentMap.get(uri.toString()) || '';
134
+ }
135
+
136
+ isReadonly() {
137
+ return true;
138
+ }
139
+
140
+ async updateContent(uri: URI): Promise<any> {
141
+ const sourceFileUri = uri.withScheme(JSON.parse(uri.query).scheme).withoutQuery().withoutFragment();
142
+ const sourceDocModelRef =
143
+ this.documentModelManager.getModelReference(sourceFileUri) ||
144
+ (await this.documentModelManager.createModelReference(sourceFileUri));
145
+ const sourceDocModel = sourceDocModelRef.instance;
146
+ const value = sourceDocModel.getText();
147
+ const replaceViewDocModelRef =
148
+ this.documentModelManager.getModelReference(uri) || (await this.documentModelManager.createModelReference(uri));
149
+ const replaceViewDocModel = replaceViewDocModelRef.instance;
150
+
151
+ replaceViewDocModel.updateContent(value);
152
+
153
+ let searchResults = this.searchBrowserService.searchResults.get(sourceFileUri.toString());
154
+
155
+ if (!searchResults) {
156
+ return '';
157
+ }
158
+
159
+ searchResults = searchResults.map((result) =>
160
+ Object.assign({}, result, {
161
+ fileUri: uri.toString(),
162
+ }),
163
+ );
164
+
165
+ await replace(
166
+ this.documentModelManager,
167
+ this.workspaceEditService,
168
+ searchResults,
169
+ this.searchBrowserService.replaceValue,
170
+ this.searchBrowserService.searchValue,
171
+ this.searchBrowserService.UIState.isUseRegexp,
172
+ );
173
+
174
+ this.contentMap.set(uri.toString(), replaceViewDocModel.getText());
175
+ }
176
+
177
+ onDidChangeContent = this.onDidChangeContentEvent.event;
178
+
179
+ delete(uri: URI) {
180
+ this.contentMap.delete(uri.toString());
181
+ }
182
+
183
+ clear() {
184
+ this.contentMap.clear();
185
+ }
186
+ }
187
+
188
+ @Injectable()
189
+ export class SearchTreeService extends Disposable implements ISearchTreeService {
190
+ private userhomePath: URI | null;
191
+
192
+ @Autowired(IContentSearchClientService)
193
+ private readonly searchBrowserService: IContentSearchClientService;
194
+
195
+ @Autowired(IWorkspaceService)
196
+ private readonly workspaceService: IWorkspaceService;
197
+
198
+ @Autowired(IEditorDocumentModelContentRegistry)
199
+ private readonly contentRegistry: IEditorDocumentModelContentRegistry;
200
+
201
+ @Autowired(ReplaceDocumentModelContentProvider)
202
+ private readonly replaceDocumentModelContentProvider: ReplaceDocumentModelContentProvider;
203
+
204
+ @Autowired(RangeHighlightDecorations)
205
+ private readonly rangeHighlightDecorations: RangeHighlightDecorations;
206
+
207
+ @Autowired(IFileServiceClient)
208
+ private readonly fileServiceClient: IFileServiceClient;
209
+
210
+ @Autowired(SearchContextKey)
211
+ private readonly searchContextKey: SearchContextKey;
212
+
213
+ @Autowired(LabelService)
214
+ private readonly labelService: LabelService;
215
+
216
+ constructor() {
217
+ super();
218
+ this.addDispose(this.rangeHighlightDecorations);
219
+ this.addDispose(
220
+ this.contentRegistry.registerEditorDocumentModelContentProvider(this.replaceDocumentModelContentProvider),
221
+ );
222
+ }
223
+
224
+ get replaceValue() {
225
+ return this.searchBrowserService.replaceValue;
226
+ }
227
+
228
+ get resultTotal() {
229
+ return this.searchBrowserService.resultTotal;
230
+ }
231
+
232
+ get contextKey() {
233
+ return this.searchContextKey;
234
+ }
235
+
236
+ initContextKey(dom: HTMLDivElement) {
237
+ this.contextKey.initScopedContext(dom);
238
+ }
239
+
240
+ removeHighlightRange() {
241
+ this.rangeHighlightDecorations.removeHighlightRange();
242
+ }
243
+
244
+ async resolveChildren(
245
+ parent?: SearchRoot | SearchFileNode,
246
+ ): Promise<(SearchRoot | SearchFileNode | SearchContentNode)[]> {
247
+ if (!parent) {
248
+ const root = new SearchRoot(this);
249
+ return [root];
250
+ } else if (parent) {
251
+ if (SearchRoot.isRoot(parent)) {
252
+ const files = this.searchBrowserService.searchResults;
253
+ if (!files) {
254
+ return [];
255
+ }
256
+ const childs: SearchFileNode[] = [];
257
+ for (const filesArray of files) {
258
+ const uri = filesArray[0];
259
+ const resultList = filesArray[1];
260
+ const _uri = new URI(uri);
261
+ const description = (await this.workspaceService.asRelativePath(_uri.parent))?.path;
262
+ if (!resultList || resultList.length < 1) {
263
+ continue;
264
+ }
265
+ childs.push(
266
+ new SearchFileNode(
267
+ this,
268
+ resultList,
269
+ description,
270
+ await this.getReadableTooltip(_uri),
271
+ this.labelService.getIcon(_uri),
272
+ _uri,
273
+ parent,
274
+ ),
275
+ );
276
+ }
277
+ return childs;
278
+ } else {
279
+ const contentResults = (parent as SearchFileNode).contentResults;
280
+ return contentResults.map((content) => {
281
+ const start = (content.renderStart || content.matchStart) - 1;
282
+ const end = start + content.matchLength;
283
+ const description = content.renderLineText || content.lineText;
284
+ return new SearchContentNode(
285
+ this,
286
+ content,
287
+ description,
288
+ { start, end },
289
+ (parent as SearchFileNode).resource,
290
+ parent as SearchFileNode,
291
+ );
292
+ });
293
+ }
294
+ }
295
+ return [];
296
+ }
297
+
298
+ @memoize
299
+ private async getCurrentUserHome() {
300
+ if (!this.userhomePath) {
301
+ try {
302
+ const userhome = await this.fileServiceClient.getCurrentUserHome();
303
+ if (userhome) {
304
+ this.userhomePath = new URI(userhome.uri);
305
+ }
306
+ } catch (err) {}
307
+ }
308
+ return this.userhomePath;
309
+ }
310
+
311
+ private async getReadableTooltip(path: URI) {
312
+ const pathStr = path.toString();
313
+ const userhomePath = await this.getCurrentUserHome();
314
+ if (!userhomePath) {
315
+ return decodeURIComponent(path.withScheme('').toString());
316
+ }
317
+ if (userhomePath.isEqualOrParent(path)) {
318
+ const userhomePathStr = userhomePath && userhomePath.toString();
319
+ return decodeURIComponent(pathStr.replace(userhomePathStr, '~'));
320
+ }
321
+ return decodeURIComponent(path.withScheme('').toString());
322
+ }
323
+ }
@@ -0,0 +1,179 @@
1
+ import cls from 'classnames';
2
+ import React, { useEffect, useState, RefObject, useRef, useCallback } from 'react';
3
+
4
+ import { IRecycleTreeHandle, RecycleTree, Button, TreeNodeEvent } from '@opensumi/ide-components';
5
+ import { localize, formatLocalize, useInjectable, CommandService } from '@opensumi/ide-core-browser';
6
+ import { ViewState } from '@opensumi/ide-core-browser';
7
+
8
+ import { ResultTotal, SEARCH_STATE } from '../../common/content-search';
9
+ import styles from '../search.module.less';
10
+
11
+ import { SearchNodeRendered, SEARCH_TREE_NODE_HEIGHT, ISearchNodeRenderedProps } from './search-node';
12
+ import { SearchModelService, SearchTreeModel } from './tree-model.service';
13
+
14
+ export interface ISearchTreeProp {
15
+ offsetTop: number;
16
+ search: string;
17
+ replace: string;
18
+ total: ResultTotal;
19
+ viewState: ViewState;
20
+ state: SEARCH_STATE;
21
+ isUseRegexp: boolean;
22
+ isMatchCase: boolean;
23
+ }
24
+
25
+ export interface ISearchResultTotalContent {
26
+ total: ResultTotal;
27
+ state: SEARCH_STATE;
28
+ model?: SearchTreeModel;
29
+ }
30
+
31
+ const ResultTotalContent = ({ total, state, model }: ISearchResultTotalContent) => {
32
+ const [collapsed, setCollapsed] = useState<boolean>(false);
33
+ const searchModelService = useInjectable<SearchModelService>(SearchModelService);
34
+ const handleFold = useCallback(() => {
35
+ const toCollapsed = !collapsed;
36
+ setCollapsed(toCollapsed);
37
+ if (toCollapsed) {
38
+ searchModelService.collapsedAll();
39
+ } else {
40
+ searchModelService.expandAll();
41
+ }
42
+ }, [collapsed, searchModelService]);
43
+
44
+ const handleRefresh = useCallback(() => {
45
+ searchModelService.refresh();
46
+ }, [searchModelService]);
47
+
48
+ useEffect(() => {
49
+ if (!model) {
50
+ return;
51
+ }
52
+ const dispose = model.root.watcher.on(TreeNodeEvent.DidChangeExpansionState, () => {
53
+ if (collapsed) {
54
+ setCollapsed(!collapsed);
55
+ }
56
+ });
57
+ return () => {
58
+ dispose.dispose();
59
+ };
60
+ }, [model, collapsed]);
61
+
62
+ if (total.resultNum > 0) {
63
+ return (
64
+ <p className={styles.result_describe}>
65
+ <span className={styles.text}>
66
+ {formatLocalize('search.files.result', String(total.resultNum), String(total.fileNum))}
67
+ </span>
68
+ <Button
69
+ className={cls(styles.result_fresh, { [styles.disabled]: state === SEARCH_STATE.doing })}
70
+ onClick={handleRefresh}
71
+ type='icon'
72
+ icon='refresh'
73
+ title={localize('search.RefreshAction.label')}
74
+ ></Button>
75
+ <Button
76
+ className={cls(styles.result_fold, { [styles.disabled]: state === SEARCH_STATE.doing })}
77
+ onClick={handleFold}
78
+ type='icon'
79
+ icon={!collapsed ? 'collapse-all' : 'expand-all'}
80
+ title={localize(
81
+ !collapsed
82
+ ? 'search.CollapseDeepestExpandedLevelAction.label'
83
+ : 'search.ExpandDeepestExpandedLevelAction.label',
84
+ )}
85
+ ></Button>
86
+ </p>
87
+ );
88
+ }
89
+ return null;
90
+ };
91
+
92
+ export const SearchTree = ({
93
+ offsetTop,
94
+ total,
95
+ state,
96
+ replace,
97
+ search,
98
+ viewState,
99
+ isUseRegexp,
100
+ isMatchCase,
101
+ }: ISearchTreeProp) => {
102
+ const [model, setModel] = useState<SearchTreeModel | undefined>();
103
+ const searchModelService = useInjectable<SearchModelService>(SearchModelService);
104
+ const commandService = useInjectable<CommandService>(CommandService);
105
+ const wrapperRef: RefObject<HTMLDivElement> = useRef(null);
106
+ const totalRef: RefObject<HTMLDivElement> = useRef(null);
107
+
108
+ const [totalHeight, setTotalHeight] = useState<number>(0);
109
+
110
+ const { handleTreeBlur, handleTreeFocus } = searchModelService;
111
+
112
+ useEffect(() => {
113
+ setModel(searchModelService.treeModel);
114
+ const disposable = searchModelService.onDidUpdateTreeModel((model?: SearchTreeModel) => {
115
+ setModel(model);
116
+ });
117
+ return () => {
118
+ disposable.dispose();
119
+ };
120
+ }, []);
121
+
122
+ useEffect(() => {
123
+ if (totalRef.current) {
124
+ setTotalHeight(totalRef.current.clientHeight);
125
+ }
126
+ }, [totalRef.current]);
127
+
128
+ const handleTreeReady = useCallback(
129
+ (handle: IRecycleTreeHandle) => {
130
+ searchModelService.handleTreeHandler(handle);
131
+ },
132
+ [searchModelService],
133
+ );
134
+
135
+ const renderTreeNode = useCallback(
136
+ (props: ISearchNodeRenderedProps) => (
137
+ <SearchNodeRendered
138
+ item={props.item}
139
+ itemType={props.itemType}
140
+ decorations={searchModelService.decorations.getDecorations(props.item as any)}
141
+ defaultLeftPadding={8}
142
+ search={search}
143
+ replace={replace}
144
+ onClick={searchModelService.handleItemClick}
145
+ onDoubleClick={searchModelService.handleItemDoubleClick}
146
+ onContextMenu={searchModelService.handleContextMenu}
147
+ leftPadding={8}
148
+ isUseRegexp={isUseRegexp}
149
+ isMatchCase={isMatchCase}
150
+ commandService={commandService}
151
+ />
152
+ ),
153
+ [model, search, replace, isUseRegexp, isMatchCase],
154
+ );
155
+
156
+ const renderSearchTree = useCallback(() => {
157
+ if (model) {
158
+ return (
159
+ <RecycleTree
160
+ height={viewState.height - offsetTop - totalHeight}
161
+ itemHeight={SEARCH_TREE_NODE_HEIGHT}
162
+ onReady={handleTreeReady}
163
+ model={model}
164
+ >
165
+ {renderTreeNode}
166
+ </RecycleTree>
167
+ );
168
+ }
169
+ }, [model, totalHeight, offsetTop, search, replace, isUseRegexp, isMatchCase, viewState]);
170
+
171
+ return (
172
+ <div className={styles.tree} tabIndex={-1} onBlur={handleTreeBlur} onFocus={handleTreeFocus} ref={wrapperRef}>
173
+ <div ref={totalRef}>
174
+ <ResultTotalContent total={total} state={state} model={model} />
175
+ </div>
176
+ {renderSearchTree()}
177
+ </div>
178
+ );
179
+ };