@opensumi/ide-outline 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 (42) hide show
  1. package/lib/browser/index.js.map +1 -1
  2. package/lib/browser/outline-node.d.ts +1 -1
  3. package/lib/browser/outline-node.d.ts.map +1 -1
  4. package/lib/browser/outline-node.define.js +10 -10
  5. package/lib/browser/outline-node.define.js.map +1 -1
  6. package/lib/browser/outline-node.js +2 -2
  7. package/lib/browser/outline-node.js.map +1 -1
  8. package/lib/browser/outline-node.module.less +5 -23
  9. package/lib/browser/outline.contribution.js +6 -6
  10. package/lib/browser/outline.contribution.js.map +1 -1
  11. package/lib/browser/outline.d.ts +3 -3
  12. package/lib/browser/outline.d.ts.map +1 -1
  13. package/lib/browser/outline.js +11 -11
  14. package/lib/browser/outline.js.map +1 -1
  15. package/lib/browser/services/outline-contextkey.service.js.map +1 -1
  16. package/lib/browser/services/outline-decoration.service.js.map +1 -1
  17. package/lib/browser/services/outline-event.service.d.ts +1 -1
  18. package/lib/browser/services/outline-event.service.d.ts.map +1 -1
  19. package/lib/browser/services/outline-event.service.js.map +1 -1
  20. package/lib/browser/services/outline-model.js.map +1 -1
  21. package/lib/browser/services/outline-model.service.d.ts +0 -2
  22. package/lib/browser/services/outline-model.service.d.ts.map +1 -1
  23. package/lib/browser/services/outline-model.service.js +5 -27
  24. package/lib/browser/services/outline-model.service.js.map +1 -1
  25. package/lib/browser/services/outline-tree.service.js +4 -4
  26. package/lib/browser/services/outline-tree.service.js.map +1 -1
  27. package/package.json +13 -12
  28. package/src/browser/index.ts +33 -0
  29. package/src/browser/outline-node.define.ts +75 -0
  30. package/src/browser/outline-node.module.less +183 -0
  31. package/src/browser/outline-node.tsx +160 -0
  32. package/src/browser/outline.contribution.ts +146 -0
  33. package/src/browser/outline.module.less +11 -0
  34. package/src/browser/outline.tsx +159 -0
  35. package/src/browser/services/outline-contextkey.service.ts +18 -0
  36. package/src/browser/services/outline-decoration.service.ts +60 -0
  37. package/src/browser/services/outline-event.service.ts +45 -0
  38. package/src/browser/services/outline-model.service.ts +584 -0
  39. package/src/browser/services/outline-model.ts +38 -0
  40. package/src/browser/services/outline-tree.service.ts +180 -0
  41. package/src/common/index.ts +32 -0
  42. package/src/index.ts +1 -0
@@ -0,0 +1,584 @@
1
+ import { Injectable, Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di';
2
+ import { DecorationsManager, Decoration, IRecycleTreeHandle, TreeNodeType, WatchEvent } from '@opensumi/ide-components';
3
+ import {
4
+ URI,
5
+ DisposableCollection,
6
+ Emitter,
7
+ CommandService,
8
+ Deferred,
9
+ Event,
10
+ MaybeNull,
11
+ MarkerManager,
12
+ IPosition,
13
+ IRange,
14
+ Disposable,
15
+ ThrottledDelayer,
16
+ path,
17
+ pSeries,
18
+ } from '@opensumi/ide-core-browser';
19
+ import { WorkbenchEditorService } from '@opensumi/ide-editor/lib/browser';
20
+ import {
21
+ DocumentSymbolStore,
22
+ INormalizedDocumentSymbol,
23
+ } from '@opensumi/ide-editor/lib/browser/breadcrumb/document-symbol';
24
+ import { EXPLORER_CONTAINER_ID } from '@opensumi/ide-explorer/lib/browser/explorer-contribution';
25
+ import { IMainLayoutService } from '@opensumi/ide-main-layout';
26
+ import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';
27
+
28
+ import { IOutlineDecorationService, OUTLINE_VIEW_ID } from '../../common';
29
+ import { OutlineTreeNode, OutlineCompositeTreeNode, OutlineRoot } from '../outline-node.define';
30
+ import styles from '../outline-node.module.less';
31
+
32
+ import { OutlineEventService } from './outline-event.service';
33
+ import { OutlineTreeModel } from './outline-model';
34
+ import { OutlineTreeService } from './outline-tree.service';
35
+
36
+ const { Path } = path;
37
+
38
+ export interface IEditorTreeHandle extends IRecycleTreeHandle {
39
+ hasDirectFocus: () => boolean;
40
+ }
41
+
42
+ @Injectable()
43
+ export class OutlineModelService {
44
+ private static DEFAULT_REFRESH_DELAY = 200;
45
+ private static DEFAULT_INIT_TREE_MODEL_DELAY = 500;
46
+
47
+ @Autowired(INJECTOR_TOKEN)
48
+ private readonly injector: Injector;
49
+
50
+ @Autowired(OutlineTreeService)
51
+ private readonly outlineTreeService: OutlineTreeService;
52
+
53
+ @Autowired(MarkerManager)
54
+ private markerManager: MarkerManager;
55
+
56
+ @Autowired(IOutlineDecorationService)
57
+ public decorationService: IOutlineDecorationService;
58
+
59
+ @Autowired(OutlineEventService)
60
+ public readonly outlineEventService: OutlineEventService;
61
+
62
+ @Autowired(WorkbenchEditorService)
63
+ private readonly editorService: WorkbenchEditorService;
64
+
65
+ @Autowired(CommandService)
66
+ public readonly commandService: CommandService;
67
+
68
+ @Autowired(DocumentSymbolStore)
69
+ private documentSymbolStore: DocumentSymbolStore;
70
+
71
+ @Autowired(IMainLayoutService)
72
+ private readonly mainLayoutService: IMainLayoutService;
73
+
74
+ private _activeTreeModel: OutlineTreeModel;
75
+ private _allTreeModels: Map<string, { treeModel: OutlineTreeModel; decoration: DecorationsManager }> = new Map();
76
+ private _whenInitTreeModelReady: Promise<void>;
77
+ private _whenActiveChangeDeferred: Deferred<void> | null;
78
+
79
+ private _whenReady: Promise<void>;
80
+
81
+ private _decorations: DecorationsManager;
82
+ private _outlineTreeHandle: IEditorTreeHandle;
83
+
84
+ private refreshDeferred: Deferred<void> | null;
85
+ private _changeEventDispatchQueue: string[] = [];
86
+
87
+ // 装饰器
88
+ private selectedDecoration: Decoration = new Decoration(styles.mod_selected); // 选中态
89
+ private focusedDecoration: Decoration = new Decoration(styles.mod_focused); // 焦点态
90
+ // 即使选中态也是焦点态的节点
91
+ private _focusedNode: OutlineCompositeTreeNode | OutlineTreeNode | undefined;
92
+ // 选中态的节点
93
+ private _selectedNodes: (OutlineCompositeTreeNode | OutlineTreeNode)[] = [];
94
+
95
+ private disposableCollection: DisposableCollection = new DisposableCollection();
96
+
97
+ private onDidRefreshedEmitter: Emitter<void> = new Emitter();
98
+ private onDidUpdateTreeModelEmitter: Emitter<OutlineTreeModel | undefined> = new Emitter();
99
+
100
+ private _ignoreFollowCursorUpdateEventTimer = 0;
101
+ private initTreeModelDelayer: ThrottledDelayer<void>;
102
+ private refreshDelayer: ThrottledDelayer<void>;
103
+
104
+ constructor() {
105
+ this.initTreeModelDelayer = new ThrottledDelayer(OutlineModelService.DEFAULT_INIT_TREE_MODEL_DELAY);
106
+ this.refreshDelayer = new ThrottledDelayer(OutlineModelService.DEFAULT_REFRESH_DELAY);
107
+ this._whenReady = this.initTreeModel();
108
+ }
109
+
110
+ get whenReady() {
111
+ return this._whenReady;
112
+ }
113
+
114
+ get flushEventQueuePromise() {
115
+ return this.refreshDeferred && this.refreshDeferred.promise;
116
+ }
117
+
118
+ get outlineTreeHandle() {
119
+ return this._outlineTreeHandle;
120
+ }
121
+
122
+ get decorations() {
123
+ return this._decorations;
124
+ }
125
+
126
+ get treeModel() {
127
+ return this._activeTreeModel;
128
+ }
129
+
130
+ // 既是选中态,也是焦点态节点
131
+ get focusedNode() {
132
+ return this._focusedNode;
133
+ }
134
+ // 是选中态,非焦点态节点
135
+ get selectedNodes() {
136
+ return this._selectedNodes;
137
+ }
138
+
139
+ get onDidRefreshed(): Event<void> {
140
+ return this.onDidRefreshedEmitter.event;
141
+ }
142
+
143
+ get onDidUpdateTreeModel(): Event<OutlineTreeModel | undefined> {
144
+ return this.onDidUpdateTreeModelEmitter.event;
145
+ }
146
+
147
+ get whenActiveChangeReady() {
148
+ return this._whenActiveChangeDeferred?.promise;
149
+ }
150
+
151
+ get whenInitTreeModelReady() {
152
+ return this._whenInitTreeModelReady;
153
+ }
154
+
155
+ get whenRefreshReady() {
156
+ return this.refreshDeferred?.promise;
157
+ }
158
+
159
+ async initTreeModelByCurrentUri(uri?: URI | null) {
160
+ await this.outlineTreeService.whenReady;
161
+ // 等待上一次刷新完成
162
+ await this.refreshDeferred?.promise;
163
+ this.outlineTreeService.currentUri = uri;
164
+ if (!!uri && this._allTreeModels.has(uri.toString())) {
165
+ const treeModelStore = this._allTreeModels.get(uri.toString());
166
+ // 初始化节点装饰器
167
+ this._activeTreeModel = treeModelStore!.treeModel;
168
+ this._decorations = treeModelStore!.decoration;
169
+ this.onDidUpdateTreeModelEmitter.fire(this._activeTreeModel);
170
+ } else if (uri) {
171
+ // 根据是否为多工作区创建不同根节点
172
+ const root = (await this.outlineTreeService.resolveChildren())[0];
173
+ if (!root) {
174
+ return;
175
+ }
176
+ const treeModel = this.injector.get<any>(OutlineTreeModel, [root]);
177
+ await treeModel.ensureReady;
178
+ this._activeTreeModel = treeModel;
179
+ // 初始化节点装饰器
180
+ const decoration = this.initDecorations(root);
181
+ if (uri) {
182
+ this._allTreeModels.set(uri?.toString(), {
183
+ treeModel,
184
+ decoration,
185
+ });
186
+ }
187
+ this.disposableCollection.push(
188
+ treeModel.onWillUpdate(() => {
189
+ if (this.focusedNode) {
190
+ // 更新树前更新下选中节点
191
+ const node = treeModel?.root.getTreeNodeById(this.focusedNode.id);
192
+ this.activeNodeDecoration(node as OutlineTreeNode, false);
193
+ } else if (this.selectedNodes.length !== 0) {
194
+ // 仅处理一下单选情况
195
+ const node = treeModel?.root.getTreeNodeById(this.selectedNodes[0].id);
196
+ this.selectNodeDecoration(node as OutlineTreeNode, false);
197
+ }
198
+ }),
199
+ );
200
+ this.onDidUpdateTreeModelEmitter.fire(treeModel);
201
+ } else {
202
+ this.onDidUpdateTreeModelEmitter.fire(undefined);
203
+ }
204
+ }
205
+
206
+ async initTreeModel() {
207
+ this._whenInitTreeModelReady = this.initTreeModelByCurrentUri(this.editorService.currentEditor?.currentUri);
208
+
209
+ await this._whenInitTreeModelReady;
210
+
211
+ this.disposableCollection.push(
212
+ this.markerManager.onMarkerChanged((resources) => {
213
+ if (
214
+ this.outlineTreeService.currentUri &&
215
+ resources.find((resource) => resource === this.outlineTreeService.currentUri!.toString())
216
+ ) {
217
+ this.refresh();
218
+ }
219
+ }),
220
+ );
221
+
222
+ this.disposableCollection.push(
223
+ this.outlineEventService.onDidActiveChange(async () => {
224
+ if (!this._whenActiveChangeDeferred) {
225
+ this._whenActiveChangeDeferred = new Deferred<void>();
226
+ }
227
+ if (!this.initTreeModelDelayer.isTriggered()) {
228
+ this.initTreeModelDelayer.cancel();
229
+ }
230
+ this.initTreeModelDelayer.trigger(async () => {
231
+ await this._whenInitTreeModelReady;
232
+ // 初始化时如果存在还未刷新的执行逻辑时,需要进行清理
233
+ if (!this.refreshDelayer.isTriggered()) {
234
+ this.refreshDelayer.cancel();
235
+ }
236
+ const uri = this.editorService.currentEditor?.currentUri;
237
+ this._whenInitTreeModelReady = this.initTreeModelByCurrentUri(uri);
238
+ this._whenActiveChangeDeferred?.resolve();
239
+ this._whenActiveChangeDeferred = null;
240
+ });
241
+ }),
242
+ );
243
+
244
+ this.disposableCollection.push(
245
+ this.outlineEventService.onDidSelectionChange(() => {
246
+ // 选取更改时不需要刷新Tree,仅需要定位选择位置即可
247
+ if (this.outlineTreeService.followCursor) {
248
+ // 如果设置了跟随光标,此时查询一下当前焦点节点
249
+ this.locateSelection(false);
250
+ }
251
+ }),
252
+ );
253
+
254
+ this.disposableCollection.push(
255
+ this.outlineEventService.onDidChange((url: URI | null) => {
256
+ this.outlineTreeService.currentUri = this.editorService.currentEditor?.currentUri;
257
+ this.refresh();
258
+ }),
259
+ );
260
+
261
+ this.disposableCollection.push(
262
+ this.outlineTreeService.onDidChange(() => {
263
+ this.refresh();
264
+ }),
265
+ );
266
+
267
+ this.disposableCollection.push(
268
+ Disposable.create(() => {
269
+ this._allTreeModels.clear();
270
+ }),
271
+ );
272
+ }
273
+
274
+ initDecorations(root) {
275
+ this._decorations = new DecorationsManager(root as any);
276
+ this._decorations.addDecoration(this.selectedDecoration);
277
+ this._decorations.addDecoration(this.focusedDecoration);
278
+ return this._decorations;
279
+ }
280
+
281
+ private locateSelection(quiet = true) {
282
+ if (!this.outlineTreeService.currentUri) {
283
+ return;
284
+ }
285
+ const symbols = this.documentSymbolStore.getDocumentSymbol(this.outlineTreeService.currentUri!);
286
+ if (symbols) {
287
+ const activeSymbols = this.findCurrentDocumentSymbol(
288
+ symbols,
289
+ this.editorService.currentEditorGroup.codeEditor.monacoEditor.getPosition(),
290
+ );
291
+ const node = this.outlineTreeService.getTreeNodeBySymbol(activeSymbols[activeSymbols.length - 1]);
292
+ if (node) {
293
+ if (this._ignoreFollowCursorUpdateEventTimer) {
294
+ this._ignoreFollowCursorUpdateEventTimer--;
295
+ return;
296
+ } else {
297
+ this.location(node as OutlineTreeNode);
298
+ }
299
+ this.activeNodeDecoration(node as OutlineTreeNode, !quiet);
300
+ }
301
+ }
302
+ }
303
+
304
+ private findCurrentDocumentSymbol(
305
+ documentSymbols: INormalizedDocumentSymbol[],
306
+ position: MaybeNull<IPosition>,
307
+ ): INormalizedDocumentSymbol[] {
308
+ const result: INormalizedDocumentSymbol[] = [];
309
+ if (!position) {
310
+ return result;
311
+ }
312
+ let toFindIn: INormalizedDocumentSymbol[] | undefined = documentSymbols;
313
+ while (toFindIn && toFindIn.length > 0) {
314
+ let found = false;
315
+ for (const documentSymbol of toFindIn) {
316
+ if (this.positionInRange(position, documentSymbol.range)) {
317
+ result.push(documentSymbol);
318
+ toFindIn = documentSymbol.children;
319
+ found = true;
320
+ break;
321
+ }
322
+ }
323
+ if (!found) {
324
+ break;
325
+ }
326
+ }
327
+ return result;
328
+ }
329
+
330
+ private positionInRange(pos: IPosition, range: IRange): boolean {
331
+ if (pos.lineNumber < range.startLineNumber) {
332
+ return false;
333
+ } else if (pos.lineNumber === range.startLineNumber) {
334
+ return pos.column >= range.startColumn;
335
+ } else if (pos.lineNumber < range.endLineNumber) {
336
+ return true;
337
+ } else if (pos.lineNumber === range.endLineNumber) {
338
+ return pos.column <= range.endColumn;
339
+ } else {
340
+ return false;
341
+ }
342
+ }
343
+ // 清空所有节点选中态
344
+ clearNodeSelectedDecoration = () => {
345
+ this._selectedNodes.forEach((file) => {
346
+ this.selectedDecoration.removeTarget(file);
347
+ });
348
+ this._selectedNodes = [];
349
+ };
350
+
351
+ // 清空其他选中/焦点态节点,更新当前焦点节点
352
+ activeNodeDecoration = (target: OutlineCompositeTreeNode | OutlineTreeNode, dispatch = true) => {
353
+ if (target) {
354
+ for (const target of this.selectedDecoration.appliedTargets.keys()) {
355
+ this.selectedDecoration.removeTarget(target);
356
+ }
357
+ for (const target of this.focusedDecoration.appliedTargets.keys()) {
358
+ this.focusedDecoration.removeTarget(target);
359
+ }
360
+ this.selectedDecoration.addTarget(target);
361
+ this.focusedDecoration.addTarget(target);
362
+ this._focusedNode = target;
363
+ this._selectedNodes = [target];
364
+
365
+ // 通知视图更新
366
+ dispatch && this.treeModel?.dispatchChange();
367
+ }
368
+ };
369
+
370
+ // 清空其他选中/焦点态节点,更新当前选中节点
371
+ selectNodeDecoration = (target: OutlineCompositeTreeNode | OutlineTreeNode, dispatch = true) => {
372
+ if (target) {
373
+ if (this.selectedNodes.length > 0) {
374
+ this.selectedNodes.forEach((node) => {
375
+ this.selectedDecoration.removeTarget(node);
376
+ });
377
+ }
378
+ if (this.focusedNode) {
379
+ this.focusedDecoration.removeTarget(this.focusedNode);
380
+ }
381
+ this.selectedDecoration.addTarget(target);
382
+ this._selectedNodes = [target];
383
+
384
+ // 通知视图更新
385
+ dispatch && this.treeModel?.dispatchChange();
386
+ }
387
+ };
388
+
389
+ // 清空其他焦点态节点,更新当前焦点节点,
390
+ // removePreFocusedDecoration 表示更新焦点节点时如果此前已存在焦点节点,之前的节点装饰器将会被移除
391
+ activeNodeFocusedDecoration = (
392
+ target: OutlineCompositeTreeNode | OutlineTreeNode,
393
+ removePreFocusedDecoration = false,
394
+ ) => {
395
+ if (this.focusedNode !== target) {
396
+ if (removePreFocusedDecoration) {
397
+ // 当存在上一次右键菜单激活的文件时,需要把焦点态的文件节点的装饰器全部移除
398
+ if (this.focusedNode) {
399
+ // 多选情况下第一次切换焦点文件
400
+ this.focusedDecoration.removeTarget(this.focusedNode);
401
+ }
402
+ } else if (this.focusedNode) {
403
+ this.focusedDecoration.removeTarget(this.focusedNode);
404
+ }
405
+ if (target) {
406
+ this.selectedDecoration.addTarget(target);
407
+ this.focusedDecoration.addTarget(target);
408
+ this._focusedNode = target;
409
+ this._selectedNodes.push(target);
410
+ }
411
+ }
412
+ // 通知视图更新
413
+ this.treeModel?.dispatchChange();
414
+ };
415
+
416
+ // 选中当前指定节点,添加装饰器属性
417
+ activeNodeSelectedDecoration = (target: OutlineCompositeTreeNode | OutlineTreeNode) => {
418
+ if (this._selectedNodes.indexOf(target) > -1) {
419
+ return;
420
+ }
421
+ if (this.selectedNodes.length > 0) {
422
+ this.selectedNodes.forEach((file) => {
423
+ this.selectedDecoration.removeTarget(file);
424
+ });
425
+ }
426
+ this._selectedNodes = [target];
427
+ this.selectedDecoration.addTarget(target);
428
+ // 通知视图更新
429
+ this.treeModel?.dispatchChange();
430
+ };
431
+
432
+ // 取消选中节点焦点
433
+ enactiveNodeDecoration = () => {
434
+ if (this.focusedNode) {
435
+ this.focusedDecoration.removeTarget(this.focusedNode);
436
+ this.treeModel?.dispatchChange();
437
+ }
438
+ this._focusedNode = undefined;
439
+ };
440
+
441
+ removeNodeDecoration() {
442
+ if (!this.decorations) {
443
+ return;
444
+ }
445
+ this.decorations.removeDecoration(this.selectedDecoration);
446
+ this.decorations.removeDecoration(this.focusedDecoration);
447
+ }
448
+
449
+ handleTreeHandler(handle: IEditorTreeHandle) {
450
+ this._outlineTreeHandle = handle;
451
+ }
452
+
453
+ handleTreeBlur = () => {
454
+ // 清空焦点状态
455
+ this.enactiveNodeDecoration();
456
+ };
457
+
458
+ handleItemClick = (item: OutlineCompositeTreeNode | OutlineTreeNode, type: TreeNodeType) => {
459
+ // 单选操作默认先更新选中状态
460
+ this.activeNodeDecoration(item);
461
+
462
+ this.revealRange(item.raw);
463
+
464
+ this._ignoreFollowCursorUpdateEventTimer++;
465
+ };
466
+
467
+ toggleDirectory = async (item: OutlineCompositeTreeNode) => {
468
+ if (item.expanded) {
469
+ this.outlineTreeHandle.collapseNode(item);
470
+ } else {
471
+ this.outlineTreeHandle.expandNode(item);
472
+ }
473
+ };
474
+
475
+ protected revealRange(symbol: INormalizedDocumentSymbol) {
476
+ const currentEditor = this.editorService.currentEditorGroup.codeEditor;
477
+ currentEditor.monacoEditor.revealLineInCenter(symbol.range.startLineNumber);
478
+ currentEditor.monacoEditor.setPosition(
479
+ new monaco.Position(symbol.range.startLineNumber, symbol.range.endLineNumber),
480
+ );
481
+ }
482
+
483
+ /**
484
+ * 刷新指定下的所有子节点
485
+ */
486
+ async refresh() {
487
+ await this.whenActiveChangeReady;
488
+ await this.whenInitTreeModelReady;
489
+ await this.whenRefreshReady;
490
+
491
+ const node: OutlineRoot = this.treeModel?.root as OutlineRoot;
492
+
493
+ if ((!node && this.editorService?.currentEditor?.currentUri) || !this.editorService?.currentEditor?.currentUri) {
494
+ // 1. 初次加载时可能还没有初始化Tree,主要原因在于没办法在outline加载时准确把握拿到currentEditor的时机
495
+ // 2. 当前没有激活的URI时,需要情况当前的Tree
496
+ this._whenInitTreeModelReady = this.initTreeModelByCurrentUri(this.editorService?.currentEditor?.currentUri);
497
+ return;
498
+ }
499
+
500
+ if (!this.refreshDelayer.isTriggered()) {
501
+ this.refreshDelayer.cancel();
502
+ }
503
+
504
+ return this.refreshDelayer.trigger(async () => {
505
+ const handler = this.mainLayoutService.getTabbarHandler(EXPLORER_CONTAINER_ID);
506
+ if (!handler || !handler.isVisible || handler.isCollapsed(OUTLINE_VIEW_ID)) {
507
+ if (this.refreshDeferred) {
508
+ this.refreshDeferred.resolve();
509
+ this.refreshDeferred = null;
510
+ }
511
+ return;
512
+ }
513
+ if (!this.refreshDeferred) {
514
+ this.refreshDeferred = new Deferred<void>();
515
+ }
516
+ this.outlineTreeService.currentUri = this.editorService?.currentEditor?.currentUri;
517
+ if (
518
+ !!node.currentUri &&
519
+ !!this.outlineTreeService.currentUri &&
520
+ this.outlineTreeService.currentUri.isEqual(node.currentUri)
521
+ ) {
522
+ // 刷新前需要更新诊断信息数据
523
+ this.decorationService.updateDiagnosisInfo(this.outlineTreeService.currentUri!);
524
+ // 因为Outline模块的节点是自展开的,不需要遍历
525
+ await node.refresh();
526
+ this.onDidRefreshedEmitter.fire();
527
+ }
528
+ this.refreshDeferred?.resolve();
529
+ this.refreshDeferred = null;
530
+ });
531
+ }
532
+
533
+ public flushEventQueue = () => {
534
+ let promise: Promise<any>;
535
+ if (!this._changeEventDispatchQueue || this._changeEventDispatchQueue.length === 0) {
536
+ return;
537
+ }
538
+ this._changeEventDispatchQueue.sort((pathA, pathB) => {
539
+ const pathADepth = Path.pathDepth(pathA);
540
+ const pathBDepth = Path.pathDepth(pathB);
541
+ return pathADepth - pathBDepth;
542
+ });
543
+ const roots = [this._changeEventDispatchQueue[0]];
544
+ for (const path of this._changeEventDispatchQueue) {
545
+ if (roots.some((root) => path.indexOf(root) === 0)) {
546
+ continue;
547
+ } else {
548
+ roots.push(path);
549
+ }
550
+ }
551
+ promise = pSeries(
552
+ roots.map((path) => async () => {
553
+ const watcher = this.treeModel.root?.watchEvents.get(path);
554
+ if (watcher && typeof watcher.callback === 'function') {
555
+ await watcher.callback({ type: WatchEvent.Changed, path });
556
+ }
557
+ return null;
558
+ }),
559
+ );
560
+ // 重置更新队列
561
+ this._changeEventDispatchQueue = [];
562
+ return promise;
563
+ };
564
+
565
+ public location = async (node: OutlineTreeNode) => {
566
+ await this.refreshDeferred?.promise;
567
+ if (!node) {
568
+ return;
569
+ }
570
+ if (!this.outlineTreeHandle) {
571
+ return;
572
+ }
573
+ node = (await this.outlineTreeHandle.ensureVisible(node, 'smart')) as OutlineTreeNode;
574
+ };
575
+
576
+ public collapseAll = async () => {
577
+ await this.refreshDeferred?.promise;
578
+ await this.treeModel?.root.collapsedAll();
579
+ };
580
+
581
+ dispose() {
582
+ this.disposableCollection.dispose();
583
+ }
584
+ }
@@ -0,0 +1,38 @@
1
+ import { Injectable, Optional } from '@opensumi/di';
2
+ import { TreeModel, TreeNodeEvent, CompositeTreeNode } from '@opensumi/ide-components';
3
+ import { ThrottledDelayer, Emitter, Event } from '@opensumi/ide-core-browser';
4
+
5
+ import { OutlineCompositeTreeNode } from '../outline-node.define';
6
+
7
+ @Injectable({ multiple: true })
8
+ export class OutlineTreeModel extends TreeModel {
9
+ static DEFAULT_FLUSH_DELAY = 100;
10
+
11
+ private flushDispatchChangeDelayer = new ThrottledDelayer<void>(OutlineTreeModel.DEFAULT_FLUSH_DELAY);
12
+ private onWillUpdateEmitter: Emitter<void> = new Emitter();
13
+
14
+ constructor(@Optional() root: OutlineCompositeTreeNode) {
15
+ super();
16
+ this.init(root);
17
+ }
18
+
19
+ get onWillUpdate(): Event<void> {
20
+ return this.onWillUpdateEmitter.event;
21
+ }
22
+
23
+ init(root: CompositeTreeNode) {
24
+ this.root = root;
25
+ // 分支更新时通知树刷新, 不是立即更新,而是延迟更新,待树稳定后再更新
26
+ // 100ms的延迟并不能保证树稳定,特别是在node_modules展开的情况下
27
+ // 但在普通使用上已经足够可用,即不会有渲染闪烁问题
28
+ this.root.watcher.on(TreeNodeEvent.BranchDidUpdate, () => {
29
+ if (!this.flushDispatchChangeDelayer.isTriggered()) {
30
+ this.flushDispatchChangeDelayer.cancel();
31
+ }
32
+ this.flushDispatchChangeDelayer.trigger(async () => {
33
+ await this.onWillUpdateEmitter.fireAndAwait();
34
+ this.dispatchChange();
35
+ });
36
+ });
37
+ }
38
+ }