Skip to content

Commit d26c0ef

Browse files
authored
fix: enable multi-root workspace support (#3848)
1 parent fe71646 commit d26c0ef

15 files changed

Lines changed: 178 additions & 52 deletions

File tree

configs/vscode-extensions.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@
143143
"version": "1.55.2"
144144
}
145145
],
146+
"ms-python": [
147+
{
148+
"name": "python",
149+
"version": "2024.4.1"
150+
},
151+
{
152+
"name": "debugpy",
153+
"version": "2024.6.0"
154+
}
155+
],
146156
"ms-vscode": [
147157
{
148158
"name": "js-debug",

packages/core-browser/src/common/common.command.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,7 @@ export namespace WORKSPACE_COMMANDS {
824824

825825
export const REMOVE_WORKSPACE_FOLDER: Command = {
826826
id: 'workspace.removeFolderFromWorkspace',
827+
label: '%workspace.removeFolderFromWorkspace%',
827828
category: CATEGORY,
828829
};
829830

packages/core-browser/src/common/common.contribution.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ import { MenuId } from '../menu/next/menu-id';
2121
import { PreferenceContribution } from '../preferences';
2222
import { AppConfig } from '../react-providers/config-provider';
2323

24-
import { COMMON_COMMANDS, EDITOR_COMMANDS, FILE_COMMANDS, TERMINAL_COMMANDS } from './common.command';
24+
import {
25+
COMMON_COMMANDS,
26+
EDITOR_COMMANDS,
27+
FILE_COMMANDS,
28+
TERMINAL_COMMANDS,
29+
WORKSPACE_COMMANDS,
30+
} from './common.command';
2531
import { ClientAppContribution } from './common.define';
2632

2733
export const inputFocusedContextKey = 'inputFocus';
@@ -155,6 +161,16 @@ export class ClientCommonContribution
155161
group: '1_open',
156162
when: 'config.application.supportsOpenWorkspace',
157163
},
164+
{
165+
command: WORKSPACE_COMMANDS.ADD_WORKSPACE_FOLDER.id,
166+
group: '1_open',
167+
when: 'config.workspace.supportMultiRootWorkspace',
168+
},
169+
{
170+
command: WORKSPACE_COMMANDS.SAVE_WORKSPACE_AS_FILE.id,
171+
group: '1_open',
172+
when: 'config.workspace.supportMultiRootWorkspace',
173+
},
158174
{
159175
command: EDITOR_COMMANDS.NEW_UNTITLED_FILE.id,
160176
group: '2_new',

packages/file-tree-next/src/browser/dialog/file-dialog.view.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Select,
1111
TreeNodeType,
1212
} from '@opensumi/ide-components';
13-
import { isMacintosh, localize, path, useInjectable } from '@opensumi/ide-core-browser';
13+
import { URI, isMacintosh, localize, path, useInjectable } from '@opensumi/ide-core-browser';
1414
import { Progress } from '@opensumi/ide-core-browser/lib/progress/progress-bar';
1515
import { IDialogService, IOpenDialogOptions, ISaveDialogOptions } from '@opensumi/ide-overlay';
1616

@@ -153,7 +153,19 @@ export const FileDialog = ({ options, model, isOpenDialog }: React.PropsWithChil
153153
}
154154
} else {
155155
if ((options as IOpenDialogOptions).canSelectFiles && type === TreeNodeType.TreeNode) {
156-
handleItemClick(item, type);
156+
const filterExts = new Set(
157+
Object.values((options as IOpenDialogOptions).filters ?? {})
158+
.flat()
159+
.map((item) => `.${item}`),
160+
);
161+
if (filterExts.size > 0) {
162+
const ext = URI.parse(item.filestat.uri).path.ext;
163+
if (filterExts.has(ext)) {
164+
handleItemClick(item, type);
165+
}
166+
} else {
167+
handleItemClick(item, type);
168+
}
157169
} else if ((options as IOpenDialogOptions).canSelectFolders && type === TreeNodeType.CompositeTreeNode) {
158170
handleItemClick(item, type);
159171
}

packages/file-tree-next/src/browser/file-tree-contribution.ts

Lines changed: 107 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import { EXPLORER_CONTAINER_ID } from '@opensumi/ide-explorer/lib/browser/explor
4949
import { IMainLayoutService, IViewsRegistry, MainLayoutContribution } from '@opensumi/ide-main-layout';
5050
import { ViewContentGroups } from '@opensumi/ide-main-layout/lib/browser/views-registry';
5151
import { IOpenDialogOptions, ISaveDialogOptions, IWindowDialogService } from '@opensumi/ide-overlay';
52-
import { DEFAULT_WORKSPACE_SUFFIX_NAME, IWorkspaceService, UNTITLED_WORKSPACE } from '@opensumi/ide-workspace';
52+
import { IWorkspaceService, UNTITLED_WORKSPACE } from '@opensumi/ide-workspace';
5353

5454
import { IFileTreeService, PasteTypes, RESOURCE_VIEW_ID } from '../common';
5555
import { Directory } from '../common/file-tree-node.define';
@@ -129,10 +129,6 @@ export class FileTreeContribution
129129
private deleteThrottler: Throttler = new Throttler();
130130
private willDeleteUris: URI[] = [];
131131

132-
get workspaceSuffixName() {
133-
return this.appConfig.workspaceSuffixName || DEFAULT_WORKSPACE_SUFFIX_NAME;
134-
}
135-
136132
initialize() {
137133
// 等待排除配置初始化结束后再初始化文件树
138134
this.workspaceService.initFileServiceExclude().then(async () => {
@@ -198,7 +194,7 @@ export class FileTreeContribution
198194
if (workspace) {
199195
const uri = new URI(workspace.uri);
200196
resourceTitle = uri.displayName;
201-
if (!workspace.isDirectory && resourceTitle.endsWith(`.${this.workspaceSuffixName}`)) {
197+
if (!workspace.isDirectory && resourceTitle.endsWith(`.${this.workspaceService.workspaceSuffixName}`)) {
202198
resourceTitle = resourceTitle.slice(0, resourceTitle.lastIndexOf('.'));
203199
if (resourceTitle === UNTITLED_WORKSPACE) {
204200
return localize('file.workspace.defaultTip');
@@ -246,6 +242,25 @@ export class FileTreeContribution
246242
group: '0_new',
247243
});
248244

245+
menuRegistry.registerMenuItem(MenuId.ExplorerContext, {
246+
command: {
247+
id: WORKSPACE_COMMANDS.ADD_WORKSPACE_FOLDER.id,
248+
label: localize('workspace.addFolderToWorkspace'),
249+
},
250+
order: 1,
251+
group: '0_workspace',
252+
when: 'config.workspace.supportMultiRootWorkspace',
253+
});
254+
menuRegistry.registerMenuItem(MenuId.ExplorerContext, {
255+
command: {
256+
id: WORKSPACE_COMMANDS.REMOVE_WORKSPACE_FOLDER.id,
257+
label: localize('workspace.removeFolderFromWorkspace'),
258+
},
259+
order: 1,
260+
group: '0_workspace',
261+
when: 'config.workspace.supportMultiRootWorkspace',
262+
});
263+
249264
menuRegistry.registerMenuItem(MenuId.ExplorerContext, {
250265
command: {
251266
id: FILE_COMMANDS.OPEN_RESOURCES.id,
@@ -872,29 +887,33 @@ export class FileTreeContribution
872887
this.appConfig.isElectronRenderer,
873888
});
874889

875-
if (this.appConfig.isElectronRenderer) {
876-
commands.registerCommand(FILE_COMMANDS.VSCODE_OPEN_FOLDER, {
877-
execute: (uri?: URI, arg?: boolean | { forceNewWindow?: boolean }) => {
878-
const windowService: IWindowService = this.injector.get(IWindowService);
879-
const options = { newWindow: true };
880-
if (typeof arg === 'boolean') {
881-
options.newWindow = arg;
882-
} else {
883-
options.newWindow = typeof arg?.forceNewWindow === 'boolean' ? arg.forceNewWindow : true;
884-
}
890+
commands.registerCommand(FILE_COMMANDS.VSCODE_OPEN_FOLDER, {
891+
execute: (uri?: URI, arg?: boolean | { forceNewWindow?: boolean }) => {
892+
const windowService: IWindowService = this.injector.get(IWindowService);
893+
const options = { newWindow: true };
894+
if (typeof arg === 'boolean') {
895+
options.newWindow = arg;
896+
} else {
897+
options.newWindow = typeof arg?.forceNewWindow === 'boolean' ? arg.forceNewWindow : true;
898+
}
885899

886-
if (uri) {
887-
return windowService.openWorkspace(uri, options);
888-
}
900+
if (uri) {
901+
return windowService.openWorkspace(uri, options);
902+
}
889903

890-
return this.commandService.executeCommand(FILE_COMMANDS.OPEN_FOLDER.id, options);
891-
},
892-
});
904+
return this.commandService.executeCommand(FILE_COMMANDS.OPEN_FOLDER.id, options);
905+
},
906+
isVisible: () => {
907+
const supportsOpenWorkspace = this.preferenceService.get<boolean>('application.supportsOpenFolder');
908+
return supportsOpenWorkspace ?? false;
909+
},
910+
});
893911

894-
commands.registerCommand(FILE_COMMANDS.OPEN_FOLDER, {
895-
execute: (options: { newWindow: boolean }) => {
912+
commands.registerCommand(FILE_COMMANDS.OPEN_FOLDER, {
913+
execute: (options: { newWindow: boolean }) => {
914+
const windowService: IWindowService = this.injector.get(IWindowService);
915+
if (this.appConfig.isElectronRenderer) {
896916
const dialogService: IElectronNativeDialogService = this.injector.get(IElectronNativeDialogService);
897-
const windowService: IWindowService = this.injector.get(IWindowService);
898917
dialogService
899918
.showOpenDialog({
900919
title: localize('workspace.openDirectory'),
@@ -905,25 +924,44 @@ export class FileTreeContribution
905924
windowService.openWorkspace(URI.file(paths[0]), options || { newWindow: true });
906925
}
907926
});
908-
},
909-
});
927+
} else {
928+
const dialogService: IWindowDialogService = this.injector.get(IWindowDialogService);
929+
dialogService
930+
.showOpenDialog({
931+
title: localize('workspace.openDirectory'),
932+
canSelectFiles: false,
933+
canSelectFolders: true,
934+
})
935+
.then((uris) => {
936+
if (uris && uris.length > 0) {
937+
windowService.openWorkspace(uris[0], options || { newWindow: true });
938+
}
939+
});
940+
}
941+
},
942+
isVisible: () => {
943+
const supportsOpenWorkspace = this.preferenceService.get<boolean>('application.supportsOpenFolder');
944+
return supportsOpenWorkspace ?? false;
945+
},
946+
});
910947

911-
commands.registerCommand(FILE_COMMANDS.OPEN_WORKSPACE, {
912-
execute: (options: { newWindow: boolean }) => {
913-
const supportsOpenWorkspace = this.preferenceService.get('application.supportsOpenWorkspace');
914-
if (!supportsOpenWorkspace) {
915-
return;
916-
}
948+
commands.registerCommand(FILE_COMMANDS.OPEN_WORKSPACE, {
949+
execute: (options?: { newWindow: boolean }) => {
950+
const supportsOpenWorkspace = this.preferenceService.get('application.supportsOpenWorkspace');
951+
if (!supportsOpenWorkspace) {
952+
return;
953+
}
954+
const windowService: IWindowService = this.injector.get(IWindowService);
955+
if (this.appConfig.isElectronRenderer) {
917956
const dialogService: IElectronNativeDialogService = this.injector.get(IElectronNativeDialogService);
918-
const windowService: IWindowService = this.injector.get(IWindowService);
919957
dialogService
920958
.showOpenDialog({
921959
title: localize('workspace.openWorkspace'),
922960
properties: ['openFile'],
923961
filters: [
924962
{
925963
name: localize('workspace.openWorkspaceTitle'),
926-
extensions: [this.workspaceSuffixName],
964+
extensions: [this.workspaceService.workspaceSuffixName],
927965
},
928966
],
929967
})
@@ -932,9 +970,31 @@ export class FileTreeContribution
932970
windowService.openWorkspace(URI.file(paths[0]), options || { newWindow: true });
933971
}
934972
});
935-
},
936-
});
937-
}
973+
} else {
974+
const dialogService: IWindowDialogService = this.injector.get(IWindowDialogService);
975+
dialogService
976+
.showOpenDialog({
977+
title: localize('workspace.openWorkspace'),
978+
canSelectFiles: true,
979+
canSelectFolders: false,
980+
canSelectMany: false,
981+
filters: {
982+
workspace: [this.workspaceService.workspaceSuffixName],
983+
},
984+
})
985+
.then((uris) => {
986+
if (uris && uris.length > 0) {
987+
const workspaceService: IWorkspaceService = this.injector.get(IWorkspaceService);
988+
workspaceService.open(uris[0], { preserveWindow: options?.newWindow ?? false });
989+
}
990+
});
991+
}
992+
},
993+
isVisible: () => {
994+
const supportsOpenWorkspace = this.preferenceService.get<boolean>('application.supportsOpenWorkspace');
995+
return supportsOpenWorkspace ?? false;
996+
},
997+
});
938998

939999
commands.registerCommand(FILE_COMMANDS.REVEAL_IN_EXPLORER, {
9401000
execute: (uriOrResource?: URI | { uri?: URI }) => {
@@ -1178,6 +1238,15 @@ export class FileTreeContribution
11781238
viewId: RESOURCE_VIEW_ID,
11791239
order: 5,
11801240
});
1241+
registry.registerItem({
1242+
id: WORKSPACE_COMMANDS.ADD_WORKSPACE_FOLDER.id,
1243+
command: WORKSPACE_COMMANDS.ADD_WORKSPACE_FOLDER.id,
1244+
label: localize('workspace.addFolderToWorkspace'),
1245+
viewId: RESOURCE_VIEW_ID,
1246+
order: 0,
1247+
group: 'file_explore_workspace',
1248+
when: 'config.workspace.supportMultiRootWorkspace',
1249+
});
11811250
}
11821251

11831252
private doDelete() {

packages/i18n/src/common/en-US.lang.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const localizationBundle = {
4949
'file.action.new.folder': 'New Folder',
5050
'file.action.refresh': 'Refresh',
5151
'file.open.folder': 'Open Folder',
52-
'file.open.workspace': 'Open Workspace',
52+
'file.open.workspace': 'Open Workspace from File ...',
5353
'file.action.collapse': 'Collapse',
5454
'file.confirm.delete': 'Are you sure you want to delete the following files?\n{0}',
5555
'file.confirm.delete.ok': 'Move to trash',
@@ -1062,6 +1062,7 @@ export const localizationBundle = {
10621062
'terminal.killProcess': 'Kill Process',
10631063
'terminal.process.unHealthy':
10641064
'*This terminal session has been timed out and killed by the system. Please open a new terminal session to proceed with operations.',
1065+
'terminal.selectCWDForNewTerminal': 'Select current working directory for new terminal',
10651066

10661067
'terminal.focusNext.inTerminalGroup': 'Terminal: Focus Next Terminal in Terminal Group',
10671068
'terminal.focusPrevious.inTerminalGroup': 'Terminal: Focus Previous Terminal in Terminal Group',
@@ -1215,7 +1216,7 @@ export const localizationBundle = {
12151216
'editor.compareAndSave.title': '{0} (on Disk) <=> {1} (Editing) ',
12161217

12171218
'workspace.openDirectory': 'Open Directory',
1218-
'workspace.addFolderToWorkspace': 'Add Folder Into Workspace ...',
1219+
'workspace.addFolderToWorkspace': 'Add Folder to Workspace ...',
12191220
'workspace.removeFolderFromWorkspace': 'Remove Folder From Workspace',
12201221
'workspace.saveWorkspaceAsFile': 'Save Workspace As ...',
12211222
'workspace.openWorkspace': 'Open Workspace',

packages/i18n/src/common/zh-CN.lang.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const localizationBundle = {
5151
'file.action.collapse': '全部折叠',
5252
'file.location': '在文件树中定位',
5353
'file.open.folder': '打开文件夹',
54-
'file.open.workspace': '打开工作区',
54+
'file.open.workspace': '从文件打开工作区',
5555
'file.confirm.delete': '确定删除下面列的文件?\n{0}',
5656
'file.confirm.delete.ok': '移入回收站',
5757
'file.confirm.delete.cancel': '取消',
@@ -719,6 +719,7 @@ export const localizationBundle = {
719719
'terminal.toggleTerminal': '切换终端面板',
720720
'terminal.killProcess': '结束进程',
721721
'terminal.process.unHealthy': '*此终端会话已被系统超时回收,请打开新的终端会话来进行操作',
722+
'terminal.selectCWDForNewTerminal': '为新 terminal 选择当前工作路径',
722723

723724
'view.command.show': '打开 {0}',
724725

packages/overlay/src/common/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export interface IDialogOptions {
103103
title?: string;
104104
defaultUri?: URI;
105105
filters?: {
106-
[name: string]: string;
106+
[name: string]: string[];
107107
};
108108
}
109109

packages/terminal-next/src/browser/terminal.client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,10 @@ export class TerminalClient extends Disposable implements ITerminalClient {
510510
if (this.workspace.isMultiRootWorkspaceOpened) {
511511
// 工作区模式下每次新建终端都需要用户手动进行一次路径选择
512512
const roots = this.workspace.tryGetRoots();
513-
const choose = await this.quickPick.show(roots.map((file) => new URI(file.uri).codeUri.fsPath));
513+
const choose = await this.quickPick.show(
514+
roots.map((file) => new URI(file.uri).codeUri.fsPath),
515+
{ placeholder: localize('terminal.selectCWDForNewTerminal') },
516+
);
514517
return choose;
515518
} else if (this.workspace.workspace) {
516519
return new URI(this.workspace.workspace?.uri).codeUri.fsPath;
@@ -530,7 +533,7 @@ export class TerminalClient extends Disposable implements ITerminalClient {
530533
const widget = this._widget;
531534
if (TerminalClient.WORKSPACE_PATH_CACHED.has(widget.group.id)) {
532535
this._workspacePath = TerminalClient.WORKSPACE_PATH_CACHED.get(widget.group.id)!;
533-
} else {
536+
} else if (!widget.recovery) {
534537
const choose = await this._pickWorkspace();
535538
if (choose) {
536539
this._workspacePath = choose;

packages/terminal-next/src/browser/terminal.controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ export class TerminalController extends WithEventBus implements ITerminalControl
355355
group,
356356
typeof session === 'string' ? session : session.client,
357357
!!session.task,
358+
false,
359+
true,
358360
);
359361
const client = await this.clientFactory(widget);
360362

0 commit comments

Comments
 (0)