@opensumi/ide-opened-editor 2.21.6 → 2.21.7-rc-1669780664.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.
@@ -0,0 +1,193 @@
1
+ import { Autowired } from '@opensumi/di';
2
+ import {
3
+ Domain,
4
+ localize,
5
+ CommandContribution,
6
+ CommandRegistry,
7
+ OPEN_EDITORS_COMMANDS,
8
+ CommandService,
9
+ FILE_COMMANDS,
10
+ EDITOR_COMMANDS,
11
+ } from '@opensumi/ide-core-browser';
12
+ import { ClientAppContribution } from '@opensumi/ide-core-browser';
13
+ import { ToolbarRegistry, TabBarToolbarContribution } from '@opensumi/ide-core-browser/lib/layout';
14
+ import { MenuContribution, IMenuRegistry, MenuId } from '@opensumi/ide-core-browser/lib/menu/next';
15
+ import { WorkbenchEditorService } from '@opensumi/ide-editor';
16
+ import { EXPLORER_CONTAINER_ID } from '@opensumi/ide-explorer/lib/browser/explorer-contribution';
17
+ import { IMainLayoutService } from '@opensumi/ide-main-layout';
18
+
19
+ import { ExplorerOpenEditorPanel } from './opened-editor';
20
+ import { EditorFile, EditorFileGroup } from './opened-editor-node.define';
21
+ import { OpenedEditorModelService } from './services/opened-editor-model.service';
22
+
23
+ export const ExplorerOpenedEditorViewId = 'file-opened-editor';
24
+
25
+ @Domain(ClientAppContribution, TabBarToolbarContribution, CommandContribution, MenuContribution)
26
+ export class OpenedEditorContribution
27
+ implements ClientAppContribution, TabBarToolbarContribution, CommandContribution, MenuContribution
28
+ {
29
+ @Autowired(IMainLayoutService)
30
+ private readonly mainLayoutService: IMainLayoutService;
31
+
32
+ @Autowired(WorkbenchEditorService)
33
+ private readonly workbenchEditorService: WorkbenchEditorService;
34
+
35
+ @Autowired(OpenedEditorModelService)
36
+ private readonly openedEditorModelService: OpenedEditorModelService;
37
+
38
+ @Autowired(CommandService)
39
+ private readonly commandService: CommandService;
40
+
41
+ async onStart() {
42
+ this.mainLayoutService.collectViewComponent(
43
+ {
44
+ id: ExplorerOpenedEditorViewId,
45
+ name: localize('opened.editors.title'),
46
+ weight: 1,
47
+ priority: 10,
48
+ collapsed: true,
49
+ component: ExplorerOpenEditorPanel,
50
+ },
51
+ EXPLORER_CONTAINER_ID,
52
+ );
53
+ }
54
+
55
+ registerCommands(commands: CommandRegistry) {
56
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.SAVE_ALL, {
57
+ execute: () => {
58
+ this.workbenchEditorService.saveAll();
59
+ },
60
+ });
61
+
62
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.CLOSE_ALL, {
63
+ execute: async () => {
64
+ await this.workbenchEditorService.closeAll();
65
+ this.openedEditorModelService.clear();
66
+ },
67
+ });
68
+
69
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.CLOSE_BY_GROUP, {
70
+ execute: (node: EditorFileGroup) => {
71
+ this.openedEditorModelService.closeAllByGroup(node);
72
+ },
73
+ });
74
+
75
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.SAVE_BY_GROUP, {
76
+ execute: (node: EditorFileGroup) => {
77
+ this.openedEditorModelService.saveAllByGroup(node);
78
+ },
79
+ });
80
+
81
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.CLOSE, {
82
+ execute: async (node: EditorFile) => {
83
+ let group;
84
+ if (node.parent && EditorFileGroup.is(node.parent as EditorFileGroup)) {
85
+ group = (node.parent as EditorFileGroup).group;
86
+ }
87
+ await this.commandService.executeCommand(EDITOR_COMMANDS.CLOSE.id, { group, uri: node.uri });
88
+ // 提前移除节点
89
+ (node.parent as EditorFileGroup).unlinkItem(node);
90
+ },
91
+ });
92
+
93
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.OPEN, {
94
+ execute: (node: EditorFile) => {
95
+ let groupIndex = 0;
96
+ if (node.parent && EditorFileGroup.is(node.parent as EditorFileGroup)) {
97
+ groupIndex = (node.parent as EditorFileGroup).group.index;
98
+ }
99
+ this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, node.uri, { groupIndex });
100
+ },
101
+ });
102
+
103
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.OPEN_TO_THE_SIDE, {
104
+ execute: (node: EditorFile) => {
105
+ let groupIndex = 0;
106
+ if (node.parent && EditorFileGroup.is(node.parent as EditorFileGroup)) {
107
+ groupIndex = (node.parent as EditorFileGroup).group.index;
108
+ }
109
+ this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, node.uri, {
110
+ groupIndex,
111
+ split: 4 /** right */,
112
+ focus: true,
113
+ });
114
+ },
115
+ });
116
+
117
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.COMPARE_SELECTED, {
118
+ execute: (node: EditorFile) => {
119
+ this.commandService.executeCommand(FILE_COMMANDS.COMPARE_SELECTED.id, node.uri, [node.uri]);
120
+ },
121
+ });
122
+
123
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.COPY_PATH, {
124
+ execute: (node: EditorFile) => {
125
+ this.commandService.executeCommand(FILE_COMMANDS.COPY_PATH.id, node.uri, [node.uri]);
126
+ },
127
+ });
128
+
129
+ commands.registerCommand(OPEN_EDITORS_COMMANDS.COPY_RELATIVE_PATH, {
130
+ execute: (node: EditorFile) => {
131
+ this.commandService.executeCommand(FILE_COMMANDS.COPY_RELATIVE_PATH.id, node.uri, [node.uri]);
132
+ },
133
+ });
134
+ }
135
+
136
+ registerToolbarItems(registry: ToolbarRegistry) {
137
+ registry.registerItem({
138
+ id: OPEN_EDITORS_COMMANDS.SAVE_ALL.id,
139
+ command: OPEN_EDITORS_COMMANDS.SAVE_ALL.id,
140
+ viewId: ExplorerOpenedEditorViewId,
141
+ label: localize('opened.editors.save.all'),
142
+ });
143
+ registry.registerItem({
144
+ id: OPEN_EDITORS_COMMANDS.CLOSE_ALL.id,
145
+ command: OPEN_EDITORS_COMMANDS.CLOSE_ALL.id,
146
+ viewId: ExplorerOpenedEditorViewId,
147
+ label: localize('opened.editors.close.all'),
148
+ });
149
+ }
150
+
151
+ registerMenus(menuRegistry: IMenuRegistry): void {
152
+ menuRegistry.registerMenuItem(MenuId.OpenEditorsContext, {
153
+ command: {
154
+ id: OPEN_EDITORS_COMMANDS.OPEN.id,
155
+ label: localize('opened.editors.open'),
156
+ },
157
+ order: 1,
158
+ group: '1_open',
159
+ });
160
+
161
+ menuRegistry.registerMenuItem(MenuId.OpenEditorsContext, {
162
+ command: {
163
+ id: OPEN_EDITORS_COMMANDS.OPEN_TO_THE_SIDE.id,
164
+ label: localize('opened.editors.openToTheSide'),
165
+ },
166
+ order: 2,
167
+ group: '1_open',
168
+ });
169
+
170
+ menuRegistry.registerMenuItem(MenuId.OpenEditorsContext, {
171
+ command: {
172
+ id: OPEN_EDITORS_COMMANDS.COMPARE_SELECTED.id,
173
+ label: localize('opened.editors.compare'),
174
+ },
175
+ group: '2_operator',
176
+ });
177
+
178
+ menuRegistry.registerMenuItem(MenuId.OpenEditorsContext, {
179
+ command: {
180
+ id: OPEN_EDITORS_COMMANDS.COPY_PATH.id,
181
+ label: localize('opened.editors.copyPath'),
182
+ },
183
+ group: '3_path',
184
+ });
185
+ menuRegistry.registerMenuItem(MenuId.OpenEditorsContext, {
186
+ command: {
187
+ id: OPEN_EDITORS_COMMANDS.COPY_RELATIVE_PATH.id,
188
+ label: localize('opened.editors.copyRelativePath'),
189
+ },
190
+ group: '3_path',
191
+ });
192
+ }
193
+ }
@@ -0,0 +1,174 @@
1
+ import React, { useState, useEffect } from 'react';
2
+
3
+ import {
4
+ RecycleTree,
5
+ IRecycleTreeHandle,
6
+ INodeRendererWrapProps,
7
+ TreeNodeType,
8
+ TreeModel,
9
+ } from '@opensumi/ide-components';
10
+ import { ViewState, CancellationToken, localize, CancellationTokenSource } from '@opensumi/ide-core-browser';
11
+ import { ProgressBar } from '@opensumi/ide-core-browser/lib/components/progressbar';
12
+ import { useInjectable } from '@opensumi/ide-core-browser/lib/react-hooks';
13
+
14
+ import styles from './index.module.less';
15
+ import { EditorTreeNode } from './opened-editor-node';
16
+ import { EditorFile, EditorFileGroup } from './opened-editor-node.define';
17
+ import { OpenedEditorModelService } from './services/opened-editor-model.service';
18
+
19
+ export const ExplorerOpenEditorPanel = ({ viewState }: React.PropsWithChildren<{ viewState: ViewState }>) => {
20
+ const OPEN_EDITOR_NODE_HEIGHT = 22;
21
+ const [isReady, setIsReady] = useState<boolean>(false);
22
+ const [isLoading, setIsLoading] = useState<boolean>(true);
23
+ const [model, setModel] = useState<TreeModel | null>();
24
+
25
+ const { width, height } = viewState;
26
+
27
+ const wrapperRef: React.RefObject<HTMLDivElement> = React.createRef();
28
+
29
+ const openedEditorModelService = useInjectable<OpenedEditorModelService>(OpenedEditorModelService);
30
+ const { decorationService, labelService, commandService } = openedEditorModelService;
31
+
32
+ const handleTreeReady = (handle: IRecycleTreeHandle) => {
33
+ openedEditorModelService.handleTreeHandler({
34
+ ...handle,
35
+ hasDirectFocus: () => wrapperRef.current === document.activeElement,
36
+ });
37
+ };
38
+
39
+ const handleItemClicked = (ev: React.MouseEvent, item: EditorFile | EditorFileGroup, type: TreeNodeType) => {
40
+ // 阻止点击事件冒泡
41
+ ev.stopPropagation();
42
+
43
+ const { handleItemClick } = openedEditorModelService;
44
+ if (!item) {
45
+ return;
46
+ }
47
+ handleItemClick(item, type);
48
+ };
49
+
50
+ const handlerContextMenu = (ev: React.MouseEvent, node: EditorFile | EditorFileGroup) => {
51
+ const { handleContextMenu } = openedEditorModelService;
52
+ handleContextMenu(ev, node);
53
+ };
54
+
55
+ const handleOuterContextMenu = (ev: React.MouseEvent) => {
56
+ const { handleContextMenu } = openedEditorModelService;
57
+ // 空白区域右键菜单
58
+ handleContextMenu(ev);
59
+ };
60
+
61
+ const handleOuterClick = (ev: React.MouseEvent) => {
62
+ // 空白区域点击,取消焦点状态
63
+ const { enactiveFileDecoration } = openedEditorModelService;
64
+ enactiveFileDecoration();
65
+ };
66
+
67
+ const ensureIsReady = async (token: CancellationToken) => {
68
+ await openedEditorModelService.whenReady;
69
+ if (token.isCancellationRequested) {
70
+ return;
71
+ }
72
+ if (openedEditorModelService.treeModel) {
73
+ setModel(openedEditorModelService.treeModel);
74
+ // 确保数据初始化完毕,减少初始化数据过程中多次刷新视图
75
+ // 这里需要重新取一下treeModel的值确保为最新的TreeModel
76
+ await openedEditorModelService.treeModel.ensureReady;
77
+ if (token.isCancellationRequested) {
78
+ return;
79
+ }
80
+ }
81
+ setIsLoading(false);
82
+ setIsReady(true);
83
+ };
84
+
85
+ useEffect(() => {
86
+ if (isReady) {
87
+ openedEditorModelService.onTreeModelChange(async (treeModel) => {
88
+ setIsLoading(true);
89
+ if (treeModel) {
90
+ // 确保数据初始化完毕,减少初始化数据过程中多次刷新视图
91
+ await treeModel.ensureReady;
92
+ }
93
+ setModel(treeModel);
94
+ setIsLoading(false);
95
+ });
96
+ }
97
+ }, [isReady]);
98
+
99
+ React.useEffect(() => {
100
+ const tokenSource = new CancellationTokenSource();
101
+ ensureIsReady(tokenSource.token);
102
+ return () => {
103
+ tokenSource.cancel();
104
+ };
105
+ }, []);
106
+
107
+ React.useEffect(() => {
108
+ const handleBlur = () => {
109
+ openedEditorModelService.handleTreeBlur();
110
+ };
111
+ wrapperRef.current?.addEventListener('blur', handleBlur, true);
112
+ return () => {
113
+ wrapperRef.current?.removeEventListener('blur', handleBlur, true);
114
+ openedEditorModelService.handleTreeBlur();
115
+ };
116
+ }, [wrapperRef.current]);
117
+
118
+ const renderTreeNode = React.useCallback(
119
+ (props: INodeRendererWrapProps) => (
120
+ <EditorTreeNode
121
+ item={props.item}
122
+ itemType={props.itemType}
123
+ decorationService={decorationService}
124
+ labelService={labelService}
125
+ commandService={commandService}
126
+ decorations={openedEditorModelService.decorations.getDecorations(props.item as any)}
127
+ onClick={handleItemClicked}
128
+ onContextMenu={handlerContextMenu}
129
+ defaultLeftPadding={22}
130
+ leftPadding={0}
131
+ />
132
+ ),
133
+ [openedEditorModelService.treeModel],
134
+ );
135
+
136
+ const renderContent = () => {
137
+ if (!isReady) {
138
+ return <span className={styles.opened_editor_empty_text}>{localize('opened.editors.empty')}</span>;
139
+ } else {
140
+ if (isLoading) {
141
+ return <ProgressBar loading />;
142
+ } else if (model) {
143
+ return (
144
+ <RecycleTree
145
+ height={height}
146
+ width={width}
147
+ itemHeight={OPEN_EDITOR_NODE_HEIGHT}
148
+ onReady={handleTreeReady}
149
+ model={model}
150
+ placeholder={() => (
151
+ <span className={styles.opened_editor_empty_text}>{localize('opened.editors.empty')}</span>
152
+ )}
153
+ >
154
+ {renderTreeNode}
155
+ </RecycleTree>
156
+ );
157
+ } else {
158
+ return <span className={styles.opened_editor_empty_text}>{localize('opened.editors.empty')}</span>;
159
+ }
160
+ }
161
+ };
162
+
163
+ return (
164
+ <div
165
+ className={styles.opened_editor_container}
166
+ tabIndex={-1}
167
+ ref={wrapperRef}
168
+ onContextMenu={handleOuterContextMenu}
169
+ onClick={handleOuterClick}
170
+ >
171
+ {renderContent()}
172
+ </div>
173
+ );
174
+ };
@@ -0,0 +1,63 @@
1
+ import { Injectable, Autowired } from '@opensumi/di';
2
+ import {
3
+ URI,
4
+ Uri,
5
+ FileDecorationsProvider,
6
+ IFileDecoration,
7
+ Emitter,
8
+ DisposableCollection,
9
+ } from '@opensumi/ide-core-browser';
10
+ import { IDecorationsService } from '@opensumi/ide-decoration';
11
+ import { IThemeService } from '@opensumi/ide-theme';
12
+
13
+ @Injectable()
14
+ export class OpenedEditorDecorationService implements FileDecorationsProvider {
15
+ @Autowired(IDecorationsService)
16
+ private readonly decorationsService: IDecorationsService;
17
+
18
+ @Autowired(IThemeService)
19
+ public readonly themeService: IThemeService;
20
+
21
+ private disposeCollection: DisposableCollection = new DisposableCollection();
22
+
23
+ private readonly onDidChangeEmitter: Emitter<void> = new Emitter();
24
+
25
+ constructor() {
26
+ this.disposeCollection.pushAll([
27
+ this.decorationsService.onDidChangeDecorations(() => {
28
+ this.onDidChangeEmitter.fire();
29
+ }),
30
+ this.themeService.onThemeChange(() => {
31
+ this.onDidChangeEmitter.fire();
32
+ }),
33
+ ]);
34
+ }
35
+
36
+ get onDidChange() {
37
+ return this.onDidChangeEmitter.event;
38
+ }
39
+
40
+ getDecoration(uri, hasChildren = false) {
41
+ // 转换URI为vscode.uri
42
+ if (uri instanceof URI) {
43
+ uri = Uri.parse(uri.toString());
44
+ }
45
+ const decoration = this.decorationsService.getDecoration(uri, hasChildren);
46
+ if (decoration) {
47
+ return {
48
+ ...decoration,
49
+ // 通过ThemeService获取颜色值
50
+ color: this.themeService.getColor({ id: decoration.color as string }),
51
+ } as IFileDecoration;
52
+ }
53
+ return {
54
+ color: '',
55
+ tooltip: '',
56
+ badge: '',
57
+ } as IFileDecoration;
58
+ }
59
+
60
+ dispose() {
61
+ this.disposeCollection.dispose();
62
+ }
63
+ }
@@ -0,0 +1,51 @@
1
+ import { Injectable } from '@opensumi/di';
2
+ import { Event, Emitter, WithEventBus, OnEvent } from '@opensumi/ide-core-browser';
3
+ import {
4
+ IResource,
5
+ IEditorGroup,
6
+ ResourceDecorationChangeEvent,
7
+ IResourceDecorationChangeEventPayload,
8
+ } from '@opensumi/ide-editor';
9
+ import { EditorGroupOpenEvent, EditorGroupCloseEvent, EditorGroupDisposeEvent } from '@opensumi/ide-editor/lib/browser';
10
+
11
+ export type OpenedEditorData = IEditorGroup | IResource;
12
+ export interface OpenedEditorEvent {
13
+ group: IEditorGroup;
14
+ resource: IResource;
15
+ }
16
+
17
+ @Injectable()
18
+ export class OpenedEditorEventService extends WithEventBus {
19
+ private _onDidChange: Emitter<OpenedEditorEvent | null> = new Emitter();
20
+ private _onDidDecorationChange: Emitter<IResourceDecorationChangeEventPayload | null> = new Emitter();
21
+ private _onDidActiveChange: Emitter<OpenedEditorEvent | null> = new Emitter();
22
+
23
+ public onDidChange: Event<OpenedEditorEvent | null> = this._onDidChange.event;
24
+ public onDidDecorationChange: Event<IResourceDecorationChangeEventPayload | null> = this._onDidDecorationChange.event;
25
+ public onDidActiveChange: Event<OpenedEditorEvent | null> = this._onDidActiveChange.event;
26
+
27
+ constructor() {
28
+ super();
29
+ }
30
+
31
+ @OnEvent(EditorGroupOpenEvent)
32
+ onEditorGroupOpenEvent(e: EditorGroupOpenEvent) {
33
+ this._onDidActiveChange.fire(e.payload);
34
+ }
35
+
36
+ @OnEvent(EditorGroupCloseEvent)
37
+ onEditorGroupCloseEvent() {
38
+ this._onDidChange.fire(null);
39
+ }
40
+
41
+ @OnEvent(EditorGroupDisposeEvent)
42
+ onEditorGroupDisposeEvent() {
43
+ this._onDidChange.fire(null);
44
+ }
45
+
46
+ // 为修改的文件添加dirty装饰
47
+ @OnEvent(ResourceDecorationChangeEvent)
48
+ onResourceDecorationChangeEvent(e: ResourceDecorationChangeEvent) {
49
+ this._onDidDecorationChange.fire(e.payload);
50
+ }
51
+ }