@opensumi/ide-opened-editor 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 (36) hide show
  1. package/lib/browser/index.js.map +1 -1
  2. package/lib/browser/opened-editor-node.d.ts +1 -1
  3. package/lib/browser/opened-editor-node.d.ts.map +1 -1
  4. package/lib/browser/opened-editor-node.define.d.ts +1 -2
  5. package/lib/browser/opened-editor-node.define.d.ts.map +1 -1
  6. package/lib/browser/opened-editor-node.define.js +5 -9
  7. package/lib/browser/opened-editor-node.define.js.map +1 -1
  8. package/lib/browser/opened-editor-node.js +1 -1
  9. package/lib/browser/opened-editor-node.js.map +1 -1
  10. package/lib/browser/opened-editor-node.module.less +2 -14
  11. package/lib/browser/opened-editor.contribution.js.map +1 -1
  12. package/lib/browser/opened-editor.d.ts.map +1 -1
  13. package/lib/browser/opened-editor.js +15 -15
  14. package/lib/browser/opened-editor.js.map +1 -1
  15. package/lib/browser/services/opened-editor-decoration.service.js.map +1 -1
  16. package/lib/browser/services/opened-editor-event.service.d.ts +1 -1
  17. package/lib/browser/services/opened-editor-event.service.d.ts.map +1 -1
  18. package/lib/browser/services/opened-editor-event.service.js.map +1 -1
  19. package/lib/browser/services/opened-editor-model.js.map +1 -1
  20. package/lib/browser/services/opened-editor-model.service.d.ts +2 -2
  21. package/lib/browser/services/opened-editor-model.service.d.ts.map +1 -1
  22. package/lib/browser/services/opened-editor-model.service.js.map +1 -1
  23. package/lib/browser/services/opened-editor-tree.service.js.map +1 -1
  24. package/package.json +16 -15
  25. package/src/browser/index.module.less +24 -0
  26. package/src/browser/index.ts +26 -0
  27. package/src/browser/opened-editor-node.define.ts +82 -0
  28. package/src/browser/opened-editor-node.module.less +182 -0
  29. package/src/browser/opened-editor-node.tsx +232 -0
  30. package/src/browser/opened-editor.contribution.ts +193 -0
  31. package/src/browser/opened-editor.tsx +183 -0
  32. package/src/browser/services/opened-editor-decoration.service.ts +63 -0
  33. package/src/browser/services/opened-editor-event.service.ts +51 -0
  34. package/src/browser/services/opened-editor-model.service.ts +549 -0
  35. package/src/browser/services/opened-editor-model.ts +47 -0
  36. package/src/browser/services/opened-editor-tree.service.ts +151 -0
@@ -0,0 +1,549 @@
1
+ import { Injectable, Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di';
2
+ import {
3
+ DecorationsManager,
4
+ Decoration,
5
+ IRecycleTreeHandle,
6
+ TreeNodeType,
7
+ WatchEvent,
8
+ TreeNode,
9
+ } from '@opensumi/ide-components';
10
+ import {
11
+ URI,
12
+ DisposableCollection,
13
+ Emitter,
14
+ IContextKeyService,
15
+ EDITOR_COMMANDS,
16
+ CommandService,
17
+ ThrottledDelayer,
18
+ Deferred,
19
+ Event,
20
+ path,
21
+ pSeries,
22
+ } from '@opensumi/ide-core-browser';
23
+ import { AbstractContextMenuService, MenuId, ICtxMenuRenderer } from '@opensumi/ide-core-browser/lib/menu/next';
24
+ import { LabelService } from '@opensumi/ide-core-browser/lib/services';
25
+ import { WorkbenchEditorService, IEditorGroup, IResource } from '@opensumi/ide-editor/lib/browser';
26
+ import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service';
27
+ import { EXPLORER_CONTAINER_ID } from '@opensumi/ide-explorer/lib/browser/explorer-contribution';
28
+ import { IMainLayoutService } from '@opensumi/ide-main-layout';
29
+
30
+ import { EditorFile, EditorFileGroup } from '../opened-editor-node.define';
31
+ import styles from '../opened-editor-node.module.less';
32
+
33
+ import { OpenedEditorDecorationService } from './opened-editor-decoration.service';
34
+ import { OpenedEditorEventService } from './opened-editor-event.service';
35
+ import { OpenedEditorModel } from './opened-editor-model';
36
+ import { OpenedEditorService } from './opened-editor-tree.service';
37
+
38
+ const { Path } = path;
39
+
40
+ export interface IEditorTreeHandle extends IRecycleTreeHandle {
41
+ hasDirectFocus: () => boolean;
42
+ }
43
+
44
+ @Injectable()
45
+ export class OpenedEditorModelService {
46
+ private static DEFAULT_FLUSH_FILE_EVENT_DELAY = 100;
47
+ private static DEFAULT_LOCATION_FLUSH_DELAY = 200;
48
+
49
+ @Autowired(INJECTOR_TOKEN)
50
+ private readonly injector: Injector;
51
+
52
+ @Autowired(ICtxMenuRenderer)
53
+ private readonly ctxMenuRenderer: ICtxMenuRenderer;
54
+
55
+ @Autowired(AbstractContextMenuService)
56
+ private readonly contextMenuService: AbstractContextMenuService;
57
+
58
+ @Autowired(IContextKeyService)
59
+ private readonly contextKeyService: IContextKeyService;
60
+
61
+ @Autowired(OpenedEditorService)
62
+ private readonly openedEditorService: OpenedEditorService;
63
+
64
+ @Autowired(LabelService)
65
+ public readonly labelService: LabelService;
66
+
67
+ @Autowired(OpenedEditorDecorationService)
68
+ public readonly decorationService: OpenedEditorDecorationService;
69
+
70
+ @Autowired(OpenedEditorEventService)
71
+ public readonly openedEditorEventService: OpenedEditorEventService;
72
+
73
+ @Autowired(WorkbenchEditorService)
74
+ private readonly editorService: WorkbenchEditorServiceImpl;
75
+
76
+ @Autowired(CommandService)
77
+ public readonly commandService: CommandService;
78
+
79
+ @Autowired(IMainLayoutService)
80
+ private readonly layoutService: IMainLayoutService;
81
+
82
+ private _treeModel: OpenedEditorModel | null;
83
+ private _whenReady: Promise<void>;
84
+
85
+ private _decorations: DecorationsManager;
86
+ private _openedEditorTreeHandle: IEditorTreeHandle;
87
+
88
+ public flushEventQueueDeferred: Deferred<void> | null;
89
+ private _eventFlushTimeout: number;
90
+ private _changeEventDispatchQueue: string[] = [];
91
+
92
+ // 装饰器
93
+ private selectedDecoration: Decoration = new Decoration(styles.mod_selected); // 选中态
94
+ private focusedDecoration: Decoration = new Decoration(styles.mod_focused); // 焦点态
95
+ private contextMenuDecoration: Decoration = new Decoration(styles.mod_actived); // 焦点态
96
+ private dirtyDecoration: Decoration = new Decoration(styles.mod_dirty); // 修改态
97
+ // 即使选中态也是焦点态的节点
98
+ private _focusedFile: EditorFileGroup | EditorFile | undefined;
99
+ // 选中态的节点
100
+ private _selectedFiles: (EditorFileGroup | EditorFile)[] = [];
101
+ // 右键菜单选择的节点
102
+ private _contextMenuFile: EditorFile | EditorFileGroup | undefined;
103
+
104
+ private disposableCollection: DisposableCollection = new DisposableCollection();
105
+
106
+ private onDidRefreshedEmitter: Emitter<void> = new Emitter();
107
+ private onTreeModelChangeEmitter: Emitter<OpenedEditorModel | null> = new Emitter();
108
+ private locationDelayer = new ThrottledDelayer<void>(OpenedEditorModelService.DEFAULT_LOCATION_FLUSH_DELAY);
109
+
110
+ // 右键菜单局部ContextKeyService
111
+ private _contextMenuContextKeyService: IContextKeyService;
112
+ private _currentDirtyNodes: EditorFile[] = [];
113
+
114
+ constructor() {
115
+ this._whenReady = this.initTreeModel();
116
+ }
117
+
118
+ get flushEventQueuePromise() {
119
+ return this.flushEventQueueDeferred && this.flushEventQueueDeferred.promise;
120
+ }
121
+
122
+ get contextMenuContextKeyService() {
123
+ if (!this._contextMenuContextKeyService) {
124
+ this._contextMenuContextKeyService = this.contextKeyService.createScoped();
125
+ }
126
+ return this._contextMenuContextKeyService;
127
+ }
128
+
129
+ get editorTreeHandle() {
130
+ return this._openedEditorTreeHandle;
131
+ }
132
+
133
+ get decorations() {
134
+ return this._decorations;
135
+ }
136
+
137
+ get treeModel() {
138
+ return this._treeModel;
139
+ }
140
+
141
+ get whenReady() {
142
+ return this._whenReady;
143
+ }
144
+
145
+ // 既是选中态,也是焦点态节点
146
+ get focusedFile() {
147
+ return this._focusedFile;
148
+ }
149
+
150
+ // 右键菜单选中的节点
151
+ get contextMenuFile() {
152
+ return this._contextMenuFile;
153
+ }
154
+
155
+ // 是选中态,非焦点态节点
156
+ get selectedFiles() {
157
+ return this._selectedFiles;
158
+ }
159
+
160
+ get onDidRefreshed(): Event<void> {
161
+ return this.onDidRefreshedEmitter.event;
162
+ }
163
+
164
+ get onTreeModelChange(): Event<OpenedEditorModel | null> {
165
+ return this.onTreeModelChangeEmitter.event;
166
+ }
167
+
168
+ async initTreeModel() {
169
+ // 根据是否为多工作区创建不同根节点
170
+ const root = (await this.openedEditorService.resolveChildren())[0];
171
+ if (!root) {
172
+ return;
173
+ }
174
+ this._treeModel = this.injector.get<any>(OpenedEditorModel, [root]);
175
+
176
+ this.initDecorations(root);
177
+
178
+ this.disposableCollection.push(
179
+ this.openedEditorService.onDirtyNodesChange((nodes) => {
180
+ for (const node of nodes) {
181
+ if (!this.dirtyDecoration.hasTarget(node as EditorFile)) {
182
+ this.dirtyDecoration.addTarget(node as EditorFile);
183
+ }
184
+ }
185
+ this._currentDirtyNodes = this._currentDirtyNodes.concat(nodes);
186
+ this.setExplorerTabBarBadge();
187
+ this.treeModel?.dispatchChange();
188
+ }),
189
+ );
190
+
191
+ this.disposableCollection.push(
192
+ this.labelService.onDidChange(() => {
193
+ this._currentDirtyNodes = [];
194
+ // 当labelService注册的对应节点图标变化时,通知视图更新
195
+ this.refresh();
196
+ }),
197
+ );
198
+
199
+ this.disposableCollection.push(
200
+ this.editorService.onActiveResourceChange(() => {
201
+ this._currentDirtyNodes = [];
202
+ this.refresh();
203
+ }),
204
+ );
205
+
206
+ this.disposableCollection.push(
207
+ this.editorService.onDidEditorGroupsChanged(() => {
208
+ this._currentDirtyNodes = [];
209
+ this.refresh();
210
+ }),
211
+ );
212
+
213
+ this.disposableCollection.push(
214
+ this.editorService.onDidCurrentEditorGroupChanged(() => {
215
+ this._currentDirtyNodes = [];
216
+ this.refresh();
217
+ }),
218
+ );
219
+
220
+ this.disposableCollection.push(
221
+ this.openedEditorEventService.onDidDecorationChange((payload) => {
222
+ let shouldUpdate = false;
223
+ if (!payload) {
224
+ return;
225
+ }
226
+ if (!this.treeModel) {
227
+ return;
228
+ }
229
+ for (let index = 0; index < this.treeModel.root.branchSize; index++) {
230
+ const node = this.treeModel.root.getTreeNodeAtIndex(index);
231
+ if (!!node && !EditorFileGroup.is(node as EditorFileGroup)) {
232
+ if ((node as EditorFile).uri.isEqual(payload.uri)) {
233
+ if (payload.decoration.dirty) {
234
+ this.dirtyDecoration.addTarget(node as EditorFile);
235
+ } else {
236
+ this.dirtyDecoration.removeTarget(node as EditorFile);
237
+ }
238
+ shouldUpdate = true;
239
+ }
240
+ }
241
+ }
242
+ if (shouldUpdate) {
243
+ this.setExplorerTabBarBadge();
244
+ this.treeModel?.dispatchChange();
245
+ }
246
+ }),
247
+ );
248
+
249
+ this.disposableCollection.push(
250
+ this.treeModel!.onWillUpdate(() => {
251
+ // 更新树前更新下选中节点
252
+ if (this.contextMenuFile) {
253
+ const node = this.treeModel?.root.getTreeNodeByPath(this.contextMenuFile.path);
254
+ if (node) {
255
+ this.activeFileDecoration(node as EditorFile, false);
256
+ }
257
+ } else if (this.focusedFile) {
258
+ const node = this.treeModel?.root.getTreeNodeByPath(this.focusedFile.path);
259
+ if (node) {
260
+ this.activeFileDecoration(node as EditorFile, false);
261
+ }
262
+ } else if (this.selectedFiles.length !== 0) {
263
+ // 仅处理一下单选情况
264
+ const node = this.treeModel?.root.getTreeNodeByPath(this.selectedFiles[0].path);
265
+ if (node) {
266
+ this.selectFileDecoration(node as EditorFile, false);
267
+ }
268
+ }
269
+ }),
270
+ );
271
+
272
+ this.disposableCollection.push(
273
+ this.openedEditorEventService.onDidChange(() => {
274
+ this.refresh();
275
+ }),
276
+ );
277
+
278
+ this.disposableCollection.push(
279
+ this.onDidRefreshed(() => {
280
+ this.dirtyDecoration.appliedTargets.clear();
281
+ // 更新dirty节点,节点可能已更新
282
+ for (const target of this._currentDirtyNodes) {
283
+ this.dirtyDecoration.addTarget(target as TreeNode);
284
+ }
285
+ const currentResource = this.editorService.currentResource;
286
+ const currentGroup = this.editorService.currentEditorGroup;
287
+ if (currentResource) {
288
+ this.location(currentResource, currentGroup);
289
+ }
290
+ this.setExplorerTabBarBadge();
291
+ }),
292
+ );
293
+ this.onTreeModelChangeEmitter.fire(this._treeModel);
294
+ }
295
+
296
+ initDecorations(root) {
297
+ this._decorations = new DecorationsManager(root as any);
298
+ this._decorations.addDecoration(this.selectedDecoration);
299
+ this._decorations.addDecoration(this.focusedDecoration);
300
+ this._decorations.addDecoration(this.contextMenuDecoration);
301
+ this._decorations.addDecoration(this.dirtyDecoration);
302
+ }
303
+
304
+ // 清空其他选中/焦点态节点,更新当前焦点节点
305
+ activeFileDecoration = (target: EditorFileGroup | EditorFile, dispatchChange = true) => {
306
+ if (this.contextMenuFile) {
307
+ this.contextMenuDecoration.removeTarget(this.contextMenuFile);
308
+ this._contextMenuFile = undefined;
309
+ }
310
+ if (target) {
311
+ if (this.selectedFiles.length > 0) {
312
+ this.selectedFiles.forEach((file) => {
313
+ this.selectedDecoration.removeTarget(file);
314
+ });
315
+ }
316
+ if (this.focusedFile) {
317
+ this.focusedDecoration.removeTarget(this.focusedFile);
318
+ }
319
+ this.selectedDecoration.addTarget(target);
320
+ this.focusedDecoration.addTarget(target);
321
+ this._focusedFile = target;
322
+ this._selectedFiles = [target];
323
+
324
+ // 通知视图更新
325
+ if (dispatchChange) {
326
+ this.treeModel?.dispatchChange();
327
+ }
328
+ }
329
+ };
330
+
331
+ // 清空其他选中/焦点态节点,更新当前选中节点
332
+ selectFileDecoration = (target: EditorFileGroup | EditorFile, dispatchChange = true) => {
333
+ if (this.contextMenuFile) {
334
+ this.contextMenuDecoration.removeTarget(this.contextMenuFile);
335
+ this._contextMenuFile = undefined;
336
+ }
337
+ if (target) {
338
+ if (this.selectedFiles.length > 0) {
339
+ this.selectedFiles.forEach((file) => {
340
+ this.selectedDecoration.removeTarget(file);
341
+ });
342
+ }
343
+ if (this.focusedFile) {
344
+ this.focusedDecoration.removeTarget(this.focusedFile);
345
+ }
346
+ this.selectedDecoration.addTarget(target);
347
+ this._selectedFiles = [target];
348
+
349
+ // 通知视图更新
350
+ if (dispatchChange) {
351
+ this.treeModel?.dispatchChange();
352
+ }
353
+ }
354
+ };
355
+
356
+ // 右键菜单焦点态切换
357
+ activeFileActivedDecoration = (target: EditorFileGroup | EditorFile) => {
358
+ if (this.contextMenuFile) {
359
+ this.contextMenuDecoration.removeTarget(this.contextMenuFile);
360
+ }
361
+ if (this.focusedFile) {
362
+ this.focusedDecoration.removeTarget(this.focusedFile);
363
+ this._focusedFile = undefined;
364
+ }
365
+ this.contextMenuDecoration.addTarget(target);
366
+ this._contextMenuFile = target;
367
+ this.treeModel?.dispatchChange();
368
+ };
369
+
370
+ // 取消选中节点焦点
371
+ enactiveFileDecoration = () => {
372
+ if (this.focusedFile) {
373
+ this.focusedDecoration.removeTarget(this.focusedFile);
374
+ this._focusedFile = undefined;
375
+ }
376
+ if (this.contextMenuFile) {
377
+ this.contextMenuDecoration.removeTarget(this.contextMenuFile);
378
+ }
379
+ this.treeModel?.dispatchChange();
380
+ };
381
+
382
+ handleContextMenu = (ev: React.MouseEvent, file?: EditorFileGroup | EditorFile) => {
383
+ if (!file) {
384
+ this.enactiveFileDecoration();
385
+ return;
386
+ }
387
+
388
+ ev.stopPropagation();
389
+ ev.preventDefault();
390
+
391
+ const { x, y } = ev.nativeEvent;
392
+
393
+ this.activeFileActivedDecoration(file);
394
+
395
+ const menus = this.contextMenuService.createMenu({
396
+ id: MenuId.OpenEditorsContext,
397
+ contextKeyService: this.contextMenuContextKeyService,
398
+ });
399
+ const menuNodes = menus.getMergedMenuNodes();
400
+ menus.dispose();
401
+ this.ctxMenuRenderer.show({
402
+ anchor: { x, y },
403
+ menuNodes,
404
+ args: [file],
405
+ });
406
+ };
407
+
408
+ handleTreeHandler(handle: IEditorTreeHandle) {
409
+ this._openedEditorTreeHandle = handle;
410
+ }
411
+
412
+ handleTreeBlur = () => {
413
+ // 清空焦点状态
414
+ this.enactiveFileDecoration();
415
+ };
416
+
417
+ handleItemClick = (item: EditorFileGroup | EditorFile, type: TreeNodeType) => {
418
+ // 单选操作默认先更新选中状态
419
+ this.activeFileDecoration(item);
420
+
421
+ if (type === TreeNodeType.TreeNode) {
422
+ this.openFile(item as EditorFile);
423
+ }
424
+ };
425
+
426
+ clear() {
427
+ this._treeModel = null;
428
+ this.onTreeModelChangeEmitter.fire(this._treeModel);
429
+ }
430
+
431
+ /**
432
+ * 刷新指定下的所有子节点
433
+ */
434
+ async refresh(node: EditorFileGroup = this.treeModel?.root as EditorFileGroup) {
435
+ if (!this._treeModel) {
436
+ await this.initTreeModel();
437
+ return;
438
+ }
439
+ if (!EditorFileGroup.is(node) && (node as EditorFileGroup).parent) {
440
+ node = (node as EditorFileGroup).parent as EditorFileGroup;
441
+ }
442
+ // 这里也可以直接调用node.refresh,但由于文件树刷新事件可能会较多
443
+ // 队列化刷新动作减少更新成本
444
+ this.queueChangeEvent(node.path, () => {
445
+ this.onDidRefreshedEmitter.fire();
446
+ });
447
+ }
448
+
449
+ // 队列化Changed事件
450
+ private queueChangeEvent(path: string, callback: any) {
451
+ if (!this.flushEventQueueDeferred) {
452
+ this.flushEventQueueDeferred = new Deferred<void>();
453
+ clearTimeout(this._eventFlushTimeout);
454
+ this._eventFlushTimeout = setTimeout(async () => {
455
+ await this.flushEventQueue()!;
456
+ this.flushEventQueueDeferred?.resolve();
457
+ this.flushEventQueueDeferred = null;
458
+ callback();
459
+ }, OpenedEditorModelService.DEFAULT_FLUSH_FILE_EVENT_DELAY) as any;
460
+ }
461
+ if (this._changeEventDispatchQueue.indexOf(path) === -1) {
462
+ this._changeEventDispatchQueue.push(path);
463
+ }
464
+ }
465
+
466
+ public flushEventQueue = () => {
467
+ let promise: Promise<any>;
468
+ if (!this._changeEventDispatchQueue || this._changeEventDispatchQueue.length === 0) {
469
+ return;
470
+ }
471
+ this._changeEventDispatchQueue.sort((pathA, pathB) => {
472
+ const pathADepth = Path.pathDepth(pathA);
473
+ const pathBDepth = Path.pathDepth(pathB);
474
+ return pathADepth - pathBDepth;
475
+ });
476
+ const roots = [this._changeEventDispatchQueue[0]];
477
+ for (const path of this._changeEventDispatchQueue) {
478
+ if (roots.some((root) => path.indexOf(root) === 0)) {
479
+ continue;
480
+ } else {
481
+ roots.push(path);
482
+ }
483
+ }
484
+ promise = pSeries(
485
+ roots.map((path) => async () => {
486
+ const watcher = this.treeModel?.root?.watchEvents.get(path);
487
+ if (watcher && typeof watcher.callback === 'function') {
488
+ await watcher.callback({ type: WatchEvent.Changed, path });
489
+ }
490
+ return null;
491
+ }),
492
+ );
493
+ // 重置更新队列
494
+ this._changeEventDispatchQueue = [];
495
+ return promise;
496
+ };
497
+
498
+ public location = async (resource: IResource | URI, group?: IEditorGroup) => {
499
+ this.locationDelayer.trigger(async () => {
500
+ await this.flushEventQueuePromise;
501
+ let node = this.openedEditorService.getEditorNodeByUri(resource, group);
502
+ if (!node) {
503
+ return;
504
+ }
505
+ node = (await this.editorTreeHandle.ensureVisible(node as EditorFile)) as EditorFile;
506
+ if (node) {
507
+ if (this.focusedFile === node) {
508
+ this.activeFileDecoration(node as EditorFile);
509
+ } else {
510
+ this.selectFileDecoration(node as EditorFile);
511
+ }
512
+ }
513
+ });
514
+ };
515
+
516
+ public openFile = (node: EditorFile) => {
517
+ let groupIndex = 0;
518
+ if (node.parent && EditorFileGroup.is(node.parent as EditorFileGroup)) {
519
+ groupIndex = (node.parent as EditorFileGroup).group.index;
520
+ }
521
+ this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, node.uri, { groupIndex, preserveFocus: true });
522
+ };
523
+
524
+ public closeFile = (node: EditorFile) => {
525
+ this.commandService.executeCommand(EDITOR_COMMANDS.CLOSE.id, node.uri);
526
+ };
527
+
528
+ public closeAllByGroup = (node: EditorFileGroup) => {
529
+ const group = node.group as IEditorGroup;
530
+ if (group) {
531
+ group.closeAll();
532
+ }
533
+ };
534
+
535
+ public saveAllByGroup = (node: EditorFileGroup) => {
536
+ const group = node.group as IEditorGroup;
537
+ if (group) {
538
+ group.saveAll();
539
+ }
540
+ };
541
+
542
+ private setExplorerTabBarBadge() {
543
+ const handler = this.layoutService.getTabbarHandler(EXPLORER_CONTAINER_ID);
544
+ const dirtyCount = this.editorService.calcDirtyCount();
545
+ if (handler) {
546
+ handler.setBadge(dirtyCount > 0 ? dirtyCount.toString() : '');
547
+ }
548
+ }
549
+ }
@@ -0,0 +1,47 @@
1
+ import { Injectable, Optional, Autowired } 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 { EditorFileGroup } from '../opened-editor-node.define';
6
+
7
+ import { OpenedEditorDecorationService } from './opened-editor-decoration.service';
8
+
9
+ @Injectable({ multiple: true })
10
+ export class OpenedEditorModel extends TreeModel {
11
+ static DEFAULT_FLUSH_DELAY = 100;
12
+
13
+ @Autowired(OpenedEditorDecorationService)
14
+ public readonly decorationService: OpenedEditorDecorationService;
15
+
16
+ private flushDispatchChangeDelayer = new ThrottledDelayer<void>(OpenedEditorModel.DEFAULT_FLUSH_DELAY);
17
+ private onWillUpdateEmitter: Emitter<void> = new Emitter();
18
+
19
+ constructor(@Optional() root: EditorFileGroup) {
20
+ super();
21
+ this.init(root);
22
+ }
23
+
24
+ get onWillUpdate(): Event<void> {
25
+ return this.onWillUpdateEmitter.event;
26
+ }
27
+
28
+ init(root: CompositeTreeNode) {
29
+ this.root = root;
30
+ // 分支更新时通知树刷新, 不是立即更新,而是延迟更新,待树稳定后再更新
31
+ // 100ms的延迟并不能保证树稳定,特别是在node_modules展开的情况下
32
+ // 但在普通使用上已经足够可用,即不会有渲染闪烁问题
33
+ this.root.watcher.on(TreeNodeEvent.BranchDidUpdate, this.doDispatchChange.bind(this));
34
+ // 主题或装饰器更新时,更新树
35
+ this.decorationService.onDidChange(this.doDispatchChange.bind(this));
36
+ }
37
+
38
+ doDispatchChange() {
39
+ if (!this.flushDispatchChangeDelayer.isTriggered()) {
40
+ this.flushDispatchChangeDelayer.cancel();
41
+ }
42
+ this.flushDispatchChangeDelayer.trigger(async () => {
43
+ await this.onWillUpdateEmitter.fireAndAwait();
44
+ this.dispatchChange();
45
+ });
46
+ }
47
+ }