From 521d20a207bebfe34ebb3a9df427477c2a8a7f3f Mon Sep 17 00:00:00 2001 From: Florent Benoit Date: Tue, 23 Jun 2020 15:52:35 +0200 Subject: [PATCH 01/10] feat(multiple-workspace-root): Multi-root workspaces based on /projects (or what has been configured as ROOT folder) Checks if there is a che.theia-workspace file. if there is one, returns that file as being a workspace root if none, generate the file by getting all clonedPath/path of the devfile projects enhancements: 1. make it optional for now (like if there is a special attribute in devfile, use that mode) 2. track new folders in /projects and add them to the che.theia-workspace file automatically (and if we delete folders) 3. allow to customize/provide different projects to be added in workspace root. Could be useful for a maven project to see only sub-folders/modules Change-Id: Idfb9370dc503bcfb5504ba37446f7f1ab171b515 Signed-off-by: Florent Benoit --- .../eclipse-che-theia-workspace/package.json | 4 +- .../src/node/che-workspace-server.ts | 78 +++++++++++++++++++ .../src/node/index.ts | 11 +++ .../src/node/workspace-backend-module.ts | 18 +++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts create mode 100644 extensions/eclipse-che-theia-workspace/src/node/index.ts create mode 100644 extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts diff --git a/extensions/eclipse-che-theia-workspace/package.json b/extensions/eclipse-che-theia-workspace/package.json index 8cae0a0e0..833d94f12 100644 --- a/extensions/eclipse-che-theia-workspace/package.json +++ b/extensions/eclipse-che-theia-workspace/package.json @@ -9,6 +9,7 @@ "src" ], "dependencies": { + "@theia/core": "next", "@eclipse-che/api": "latest", "@theia/workspace": "next", "@eclipse-che/theia-remote-api": "^0.0.1", @@ -31,7 +32,8 @@ "license": "EPL-2.0", "theiaExtensions": [ { - "frontend": "lib/browser/che-workspace-module" + "frontend": "lib/browser/che-workspace-module", + "backend": "lib/node/workspace-backend-module" } ] } diff --git a/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts new file mode 100644 index 000000000..512881184 --- /dev/null +++ b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts @@ -0,0 +1,78 @@ +/********************************************************************** + * Copyright (c) 2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import { injectable, inject } from 'inversify'; +import { DefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server'; +import { CheApiService } from '@eclipse-che/theia-plugin-ext/lib/common/che-protocol'; +import { FileUri } from '@theia/core/lib/node'; +import * as fs from 'fs-extra'; +import * as path from 'path'; + +interface TheiaWorkspace { + folders: TheiaWorkspacePath[] +}; + +interface TheiaWorkspacePath { + path: string +}; + +@injectable() +export class CheWorkspaceServer extends DefaultWorkspaceServer { + + @inject(CheApiService) + private cheApiService: CheApiService; + + // override any workspace that could have been defined through CLI and use entries from the devfile + // if not possible, use default method + protected async getRoot(): Promise { + + let projectsRoot: string; + if (process.env.CHE_PROJECTS_ROOT) { + projectsRoot = process.env.CHE_PROJECTS_ROOT; + } else { + projectsRoot = '/projects'; + } + + // first, check if we have a che.theia-workspace file + const cheTheiaWorkspaceFile = path.resolve(projectsRoot, 'che.theia-workspace'); + const exists = await fs.pathExists(cheTheiaWorkspaceFile); + if (exists) { + return FileUri.create(cheTheiaWorkspaceFile).toString(); + } + + // no, then create the file + + const workspace = await this.cheApiService.currentWorkspace(); + const devfile = workspace.devfile; + if (devfile) { + const projects = devfile.projects; + if (projects) { + // create a struc for each project + const theiaWorkspace: TheiaWorkspace = { folders: [] }; + for (const project of projects) { + const projectPath = project.clonePath ? path.join(projectsRoot, project.clonePath) : path.join(projectsRoot, project.name!); + // check parent folder exists + const parentDir = path.resolve(projectPath, '..'); + await fs.ensureDir(parentDir); + theiaWorkspace.folders.push({ path: FileUri.create(projectPath).toString() }); + } + + // now, need to write the content of this file + await fs.writeFile(cheTheiaWorkspaceFile, JSON.stringify(theiaWorkspace), { encoding: 'utf8' }); + + // return this content + return FileUri.create(cheTheiaWorkspaceFile).toString(); + } + } + + return super.getRoot(); + } + +} diff --git a/extensions/eclipse-che-theia-workspace/src/node/index.ts b/extensions/eclipse-che-theia-workspace/src/node/index.ts new file mode 100644 index 000000000..fb9c9d431 --- /dev/null +++ b/extensions/eclipse-che-theia-workspace/src/node/index.ts @@ -0,0 +1,11 @@ +/********************************************************************** + * Copyright (c) 2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +export * from './workspace-backend-module'; diff --git a/extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts b/extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts new file mode 100644 index 000000000..0fb883a5d --- /dev/null +++ b/extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts @@ -0,0 +1,18 @@ +/********************************************************************** + * Copyright (c) 2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import { ContainerModule } from 'inversify'; +import { CheWorkspaceServer } from './che-workspace-server'; +import { WorkspaceServer } from '@theia/workspace/lib/common'; + +export default new ContainerModule((bind, unbind, isBound, rebind) => { + bind(CheWorkspaceServer).toSelf().inSingletonScope(); + rebind(WorkspaceServer).toService(CheWorkspaceServer); +}); From 0f38c80a62aabc1f72b07673893c28e8b10c187f Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Thu, 8 Oct 2020 16:59:50 +0300 Subject: [PATCH 02/10] Replace CheApiService by WorkspaceService Signed-off-by: Roman Nikitenko --- .../src/node/che-workspace-server.ts | 99 ++++++++++--------- .../src/node/workspace-backend-module.ts | 6 +- 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts index 512881184..c14a7d11a 100644 --- a/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts +++ b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts @@ -8,71 +8,72 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ -import { injectable, inject } from 'inversify'; -import { DefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server'; -import { CheApiService } from '@eclipse-che/theia-plugin-ext/lib/common/che-protocol'; -import { FileUri } from '@theia/core/lib/node'; import * as fs from 'fs-extra'; import * as path from 'path'; +import { inject, injectable } from 'inversify'; + +import { DefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server'; +import { FileUri } from '@theia/core/lib/node'; +import { WorkspaceService } from '@eclipse-che/theia-remote-api/lib/common/workspace-service'; + interface TheiaWorkspace { - folders: TheiaWorkspacePath[] -}; + folders: TheiaWorkspacePath[]; +} interface TheiaWorkspacePath { - path: string -}; + path: string; +} @injectable() export class CheWorkspaceServer extends DefaultWorkspaceServer { + @inject(WorkspaceService) + protected workspaceService: WorkspaceService; - @inject(CheApiService) - private cheApiService: CheApiService; + // override any workspace that could have been defined through CLI and use entries from the devfile + // if not possible, use default method + protected async getRoot(): Promise { + let projectsRoot: string; + if (process.env.CHE_PROJECTS_ROOT) { + projectsRoot = process.env.CHE_PROJECTS_ROOT; + } else { + projectsRoot = '/projects'; + } - // override any workspace that could have been defined through CLI and use entries from the devfile - // if not possible, use default method - protected async getRoot(): Promise { + // first, check if we have a che.theia-workspace file + const cheTheiaWorkspaceFile = path.resolve(projectsRoot, 'che.theia-workspace'); + const exists = await fs.pathExists(cheTheiaWorkspaceFile); + if (exists) { + return FileUri.create(cheTheiaWorkspaceFile).toString(); + } - let projectsRoot: string; - if (process.env.CHE_PROJECTS_ROOT) { - projectsRoot = process.env.CHE_PROJECTS_ROOT; - } else { - projectsRoot = '/projects'; - } + // no, then create the file - // first, check if we have a che.theia-workspace file - const cheTheiaWorkspaceFile = path.resolve(projectsRoot, 'che.theia-workspace'); - const exists = await fs.pathExists(cheTheiaWorkspaceFile); - if (exists) { - return FileUri.create(cheTheiaWorkspaceFile).toString(); + const workspace = await this.workspaceService.currentWorkspace(); + const devfile = workspace.devfile; + if (devfile) { + const projects = devfile.projects; + if (projects) { + // create a struc for each project + const theiaWorkspace: TheiaWorkspace = { folders: [] }; + for (const project of projects) { + const projectPath = project.clonePath + ? path.join(projectsRoot, project.clonePath) + : path.join(projectsRoot, project.name!); + // check parent folder exists + const parentDir = path.resolve(projectPath, '..'); + await fs.ensureDir(parentDir); + theiaWorkspace.folders.push({ path: FileUri.create(projectPath).toString() }); } - // no, then create the file - - const workspace = await this.cheApiService.currentWorkspace(); - const devfile = workspace.devfile; - if (devfile) { - const projects = devfile.projects; - if (projects) { - // create a struc for each project - const theiaWorkspace: TheiaWorkspace = { folders: [] }; - for (const project of projects) { - const projectPath = project.clonePath ? path.join(projectsRoot, project.clonePath) : path.join(projectsRoot, project.name!); - // check parent folder exists - const parentDir = path.resolve(projectPath, '..'); - await fs.ensureDir(parentDir); - theiaWorkspace.folders.push({ path: FileUri.create(projectPath).toString() }); - } - - // now, need to write the content of this file - await fs.writeFile(cheTheiaWorkspaceFile, JSON.stringify(theiaWorkspace), { encoding: 'utf8' }); - - // return this content - return FileUri.create(cheTheiaWorkspaceFile).toString(); - } - } + // now, need to write the content of this file + await fs.writeFile(cheTheiaWorkspaceFile, JSON.stringify(theiaWorkspace), { encoding: 'utf8' }); - return super.getRoot(); + // return this content + return FileUri.create(cheTheiaWorkspaceFile).toString(); + } } + return super.getRoot(); + } } diff --git a/extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts b/extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts index 0fb883a5d..4a3d6a2bb 100644 --- a/extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts +++ b/extensions/eclipse-che-theia-workspace/src/node/workspace-backend-module.ts @@ -8,11 +8,11 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ -import { ContainerModule } from 'inversify'; import { CheWorkspaceServer } from './che-workspace-server'; +import { ContainerModule } from 'inversify'; import { WorkspaceServer } from '@theia/workspace/lib/common'; export default new ContainerModule((bind, unbind, isBound, rebind) => { - bind(CheWorkspaceServer).toSelf().inSingletonScope(); - rebind(WorkspaceServer).toService(CheWorkspaceServer); + bind(CheWorkspaceServer).toSelf().inSingletonScope(); + rebind(WorkspaceServer).toService(CheWorkspaceServer); }); From cf6fbf93b3082337485dfde974ca4c7eafccace6 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Thu, 19 Nov 2020 09:05:04 +0200 Subject: [PATCH 03/10] Add project as a workspace folder when the project has cloned Signed-off-by: Roman Nikitenko --- .../src/browser/che-navigator-widget.tsx | 26 ++++++++++ .../src/browser/che-workspace-module.ts | 51 +++++++++++++++++-- .../src/node/che-workspace-server.ts | 14 ----- .../src/export/export-configs-manager.ts | 18 +++++++ .../task-plugin/src/task-plugin-backend.ts | 2 +- .../workspace-plugin/src/theia-commands.ts | 9 ++++ .../src/workspace-projects-manager.ts | 23 ++++++++- 7 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 extensions/eclipse-che-theia-workspace/src/browser/che-navigator-widget.tsx diff --git a/extensions/eclipse-che-theia-workspace/src/browser/che-navigator-widget.tsx b/extensions/eclipse-che-theia-workspace/src/browser/che-navigator-widget.tsx new file mode 100644 index 000000000..832802dd7 --- /dev/null +++ b/extensions/eclipse-che-theia-workspace/src/browser/che-navigator-widget.tsx @@ -0,0 +1,26 @@ +/********************************************************************** + * Copyright (c) 2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import * as React from 'react'; + +import { injectable } from 'inversify'; + +import { FileNavigatorWidget } from '@theia/navigator/lib/browser/navigator-widget'; + +@injectable() +export class CheFileNavigatorWidget extends FileNavigatorWidget { + protected renderEmptyMultiRootWorkspace(): React.ReactNode { + return ( +
+
No projects in the workspace yet
+
+ ); + } +} diff --git a/extensions/eclipse-che-theia-workspace/src/browser/che-workspace-module.ts b/extensions/eclipse-che-theia-workspace/src/browser/che-workspace-module.ts index 4e5cfa690..84cdca7b8 100644 --- a/extensions/eclipse-che-theia-workspace/src/browser/che-workspace-module.ts +++ b/extensions/eclipse-che-theia-workspace/src/browser/che-workspace-module.ts @@ -7,15 +7,31 @@ * * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ - import { CommandContribution, MenuContribution } from '@theia/core/lib/common'; +import { Container, ContainerModule, interfaces } from 'inversify'; +import { FileTree, FileTreeModel, FileTreeWidget, createFileTreeContainer } from '@theia/filesystem/lib/browser'; +import { + FrontendApplicationContribution, + Tree, + TreeDecoratorService, + TreeModel, + TreeProps, +} from '@theia/core/lib/browser'; +import { + NavigatorDecoratorService, + NavigatorTreeDecorator, +} from '@theia/navigator/lib/browser/navigator-decorator-service'; +import { CheFileNavigatorWidget } from './che-navigator-widget'; import { CheWorkspaceContribution } from './che-workspace-contribution'; import { CheWorkspaceController } from './che-workspace-controller'; -import { ContainerModule } from 'inversify'; import { ExplorerContribution } from './explorer-contribution'; -import { FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { FILE_NAVIGATOR_PROPS } from '@theia/navigator/lib/browser/navigator-container'; +import { FileNavigatorModel } from '@theia/navigator/lib/browser/navigator-model'; +import { FileNavigatorTree } from '@theia/navigator/lib/browser/navigator-tree'; +import { FileNavigatorWidget } from '@theia/navigator/lib/browser/navigator-widget'; import { QuickOpenCheWorkspace } from './che-quick-open-workspace'; +import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(QuickOpenCheWorkspace).toSelf().inSingletonScope(); @@ -27,4 +43,33 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ExplorerContribution).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).to(ExplorerContribution); + + rebind(FileNavigatorWidget).toDynamicValue(ctx => createFileNavigatorWidget(ctx.container)); }); + +export function createFileNavigatorContainer(parent: interfaces.Container): Container { + const child = createFileTreeContainer(parent); + + child.unbind(FileTree); + child.bind(FileNavigatorTree).toSelf(); + child.rebind(Tree).toService(FileNavigatorTree); + + child.unbind(FileTreeModel); + child.bind(FileNavigatorModel).toSelf(); + child.rebind(TreeModel).toService(FileNavigatorModel); + + child.unbind(FileTreeWidget); + child.bind(CheFileNavigatorWidget).toSelf(); + + child.rebind(TreeProps).toConstantValue(FILE_NAVIGATOR_PROPS); + + child.bind(NavigatorDecoratorService).toSelf().inSingletonScope(); + child.rebind(TreeDecoratorService).toService(NavigatorDecoratorService); + bindContributionProvider(child, NavigatorTreeDecorator); + + return child; +} + +export function createFileNavigatorWidget(parent: interfaces.Container): CheFileNavigatorWidget { + return createFileNavigatorContainer(parent).get(CheFileNavigatorWidget); +} diff --git a/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts index c14a7d11a..ab765bf30 100644 --- a/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts +++ b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts @@ -54,22 +54,8 @@ export class CheWorkspaceServer extends DefaultWorkspaceServer { if (devfile) { const projects = devfile.projects; if (projects) { - // create a struc for each project const theiaWorkspace: TheiaWorkspace = { folders: [] }; - for (const project of projects) { - const projectPath = project.clonePath - ? path.join(projectsRoot, project.clonePath) - : path.join(projectsRoot, project.name!); - // check parent folder exists - const parentDir = path.resolve(projectPath, '..'); - await fs.ensureDir(parentDir); - theiaWorkspace.folders.push({ path: FileUri.create(projectPath).toString() }); - } - - // now, need to write the content of this file await fs.writeFile(cheTheiaWorkspaceFile, JSON.stringify(theiaWorkspace), { encoding: 'utf8' }); - - // return this content return FileUri.create(cheTheiaWorkspaceFile).toString(); } } diff --git a/plugins/task-plugin/src/export/export-configs-manager.ts b/plugins/task-plugin/src/export/export-configs-manager.ts index ea623fd21..5b5b5b375 100644 --- a/plugins/task-plugin/src/export/export-configs-manager.ts +++ b/plugins/task-plugin/src/export/export-configs-manager.ts @@ -8,6 +8,9 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ +import * as startPoint from '../task-plugin-backend'; +import * as theia from '@theia/plugin'; + import { inject, injectable, multiInject } from 'inversify'; import { CheWorkspaceClient } from '../che-workspace-client'; @@ -42,6 +45,21 @@ export class ExportConfigurationsManager { @multiInject(ConfigurationsExporter) protected readonly exporters: ConfigurationsExporter[]; + init(): void { + this.export(); + + theia.workspace.onDidChangeWorkspaceFolders( + event => { + const workspaceFolders = event.added; + if (workspaceFolders && workspaceFolders.length > 0) { + this.export(); + } + }, + undefined, + startPoint.getSubscriptions() + ); + } + async export(): Promise { const exportPromises = []; const cheCommands = await this.cheWorkspaceClient.getCommands(); diff --git a/plugins/task-plugin/src/task-plugin-backend.ts b/plugins/task-plugin/src/task-plugin-backend.ts index 31f2efb49..70c914f5f 100644 --- a/plugins/task-plugin/src/task-plugin-backend.ts +++ b/plugins/task-plugin/src/task-plugin-backend.ts @@ -53,7 +53,7 @@ export async function start(context: theia.PluginContext): Promise { await che.task.addTaskSubschema(CHE_TASK_SCHEMA); const exportConfigurationsManager = container.get(ExportConfigurationsManager); - exportConfigurationsManager.export(); + exportConfigurationsManager.init(); const taskStatusHandler = container.get(TaskStatusHandler); taskStatusHandler.init(); diff --git a/plugins/workspace-plugin/src/theia-commands.ts b/plugins/workspace-plugin/src/theia-commands.ts index 20d40daa7..052ab779a 100644 --- a/plugins/workspace-plugin/src/theia-commands.ts +++ b/plugins/workspace-plugin/src/theia-commands.ts @@ -45,6 +45,7 @@ function isDevfileProjectConfig( export interface TheiaImportCommand { execute(): Promise; + getProjectPath(): string; } export function buildProjectImportCommand( @@ -119,6 +120,10 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { this.projectsRoot = projectsRoot; } + getProjectPath(): string { + return this.projectPath; + } + clone(): PromiseLike { return theia.window.withProgress( { @@ -316,6 +321,10 @@ export class TheiaImportZipCommand implements TheiaImportCommand { } } + getProjectPath(): string { + return this.projectDir; + } + async execute(): Promise { const importZip = async ( progress: theia.Progress<{ message?: string; increment?: number }>, diff --git a/plugins/workspace-plugin/src/workspace-projects-manager.ts b/plugins/workspace-plugin/src/workspace-projects-manager.ts index 1f8d3322d..6c3d5352f 100644 --- a/plugins/workspace-plugin/src/workspace-projects-manager.ts +++ b/plugins/workspace-plugin/src/workspace-projects-manager.ts @@ -42,7 +42,11 @@ export class WorkspaceProjectsManager { const workspace = await che.workspace.getCurrentWorkspace(); const cloneCommandList = await this.buildCloneCommands(workspace); - await this.executeCloneCommands(cloneCommandList); + + const cloningPromise = this.executeCloneCommands(cloneCommandList); + theia.window.withProgress({ location: { viewId: 'explorer' } }, () => cloningPromise); + + await cloningPromise; await this.startSyncWorkspaceProjects(); } @@ -71,7 +75,22 @@ export class WorkspaceProjectsManager { } theia.window.showInformationMessage('Che Workspace: Starting importing projects.'); - await Promise.all(cloneCommandList.map(cloneCommand => cloneCommand.execute())); + + const cloningPromises: PromiseLike[] = []; + for (const cloneCommand of cloneCommandList) { + const cloningPromise = cloneCommand.execute(); + + cloningPromises.push(cloningPromise); + + cloningPromise.then(() => { + const workspaceFolders = theia.workspace.workspaceFolders; + theia.workspace.updateWorkspaceFolders(workspaceFolders ? workspaceFolders.length : 0, undefined, { + uri: theia.Uri.file(cloneCommand.getProjectPath()), + }); + }); + } + + await Promise.all(cloningPromises); theia.window.showInformationMessage('Che Workspace: Finished importing projects.'); onDidCloneSourcesEmitter.fire(); } From b87256b77ae423e3d35c7e4515c0f53010d1499b Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Mon, 30 Nov 2020 19:36:19 +0200 Subject: [PATCH 04/10] Ensure config storage directories exist Signed-off-by: Roman Nikitenko --- .../src/node/plugin-remote-init.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts b/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts index f54e36c7f..4b041e654 100644 --- a/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts +++ b/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts @@ -46,12 +46,22 @@ import pluginExtBackendModule from '@theia/plugin-ext/lib/plugin-ext-backend-mod import pluginRemoteBackendModule from './plugin-remote-backend-module'; import pluginVscodeBackendModule from '@theia/plugin-ext-vscode/lib/node/plugin-vscode-backend-module'; +const DEFAULT_THEIA_HOME_DIR = '/home/theia/'; +const DEFAULT_THEIA_DEV_HOME_DIR = '/home/theia-dev'; + interface CheckAliveWS extends ws { alive: boolean; } -function modifyPathToLocal(origPath: string): string { - return path.join(os.homedir(), origPath.substr(0, '/home/theia/'.length)); +function modifyPathToLocal(originalPath: string): string { + if (originalPath.startsWith(DEFAULT_THEIA_HOME_DIR)) { + return path.join(os.homedir(), originalPath.substring(DEFAULT_THEIA_HOME_DIR.length)); + } + + if (originalPath.startsWith(DEFAULT_THEIA_DEV_HOME_DIR)) { + return path.join(os.homedir(), originalPath.substring(DEFAULT_THEIA_DEV_HOME_DIR.length)); + } + return originalPath; } @injectable() @@ -127,10 +137,22 @@ export class PluginRemoteInit { const originalStart = PluginManagerExtImpl.prototype.$start; PluginManagerExtImpl.prototype.$start = async function (params: PluginManagerStartParams): Promise { const { hostLogPath, hostStoragePath, hostGlobalStoragePath } = params.configStorage; + + const overriddenLogPath = modifyPathToLocal(hostLogPath); + await fs.ensureDir(overriddenLogPath); + + const overriddenStoragePath = hostStoragePath ? modifyPathToLocal(hostStoragePath) : undefined; + if (overriddenStoragePath) { + await fs.ensureDir(overriddenStoragePath); + } + + const overriddenGlobalStoragePath = modifyPathToLocal(hostGlobalStoragePath); + await fs.ensureDir(overriddenGlobalStoragePath); + params.configStorage = { - hostLogPath: modifyPathToLocal(hostLogPath), - hostStoragePath: hostStoragePath ? modifyPathToLocal(hostStoragePath) : undefined, - hostGlobalStoragePath: modifyPathToLocal(hostGlobalStoragePath), + hostLogPath: overriddenLogPath, + hostStoragePath: overriddenStoragePath, + hostGlobalStoragePath: overriddenGlobalStoragePath, }; // call original method return originalStart.call(this, params); From a0f24fc3d017f29efaab2f8895b6d55d71d2da73 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Mon, 7 Dec 2020 09:52:16 +0200 Subject: [PATCH 05/10] Ensure workspace folder creation when a project is imported Signed-off-by: Roman Nikitenko --- .../src/workspace-projects-manager.ts | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/plugins/workspace-plugin/src/workspace-projects-manager.ts b/plugins/workspace-plugin/src/workspace-projects-manager.ts index 6c3d5352f..2f1ca6c75 100644 --- a/plugins/workspace-plugin/src/workspace-projects-manager.ts +++ b/plugins/workspace-plugin/src/workspace-projects-manager.ts @@ -34,12 +34,18 @@ export class WorkspaceProjectsManager { constructor(protected pluginContext: theia.PluginContext, protected projectsRoot: string) {} - async run(): Promise { - if (!theia.workspace.name) { - // no workspace opened, so nothing to clone / watch - return; - } + getProjectPath(project: cheApi.workspace.devfile.Project): string { + return project.clonePath + ? path.join(this.projectsRoot, project.clonePath) + : path.join(this.projectsRoot, project.name!); + } + + getProjects(workspace: cheApi.workspace.Workspace): cheApi.workspace.devfile.Project[] { + const projects = workspace.devfile!.projects; + return projects ? projects : []; + } + async run(): Promise { const workspace = await che.workspace.getCurrentWorkspace(); const cloneCommandList = await this.buildCloneCommands(workspace); @@ -48,27 +54,44 @@ export class WorkspaceProjectsManager { await cloningPromise; + this.ensureWorkspaceFolders(workspace); + await this.startSyncWorkspaceProjects(); } async buildCloneCommands(workspace: cheApi.workspace.Workspace): Promise { const instance = this; - const projects = workspace.devfile!.projects; - if (!projects) { - return []; - } + const projects = this.getProjects(workspace); return projects - .filter(project => { - const projectPath = project.clonePath - ? path.join(instance.projectsRoot, project.clonePath) - : path.join(instance.projectsRoot, project.name!); - return !fs.existsSync(projectPath); - }) + .filter(project => !fs.existsSync(this.getProjectPath(project))) .map(project => buildProjectImportCommand(project, instance.projectsRoot)!); } + ensureWorkspaceFolders(workspace: cheApi.workspace.Workspace): void { + this.getProjects(workspace) + .map(project => this.getProjectPath(project)) + .filter(projectPath => fs.existsSync(projectPath)) + .forEach(projectPath => this.addWorkspaceFolder(projectPath)); + } + + addWorkspaceFolder(projectPath: string): void { + const workspaceFolders = theia.workspace.workspaceFolders; + if (workspaceFolders && workspaceFolders.length > 0) { + const pathsList = workspaceFolders.map(folder => folder.uri.path); + if (pathsList.indexOf(projectPath) === -1) { + theia.workspace.updateWorkspaceFolders(workspaceFolders ? workspaceFolders.length : 0, undefined, { + uri: theia.Uri.file(projectPath), + }); + } + } else { + theia.workspace.updateWorkspaceFolders(0, undefined, { + uri: theia.Uri.file(projectPath), + }); + } + } + private async executeCloneCommands(cloneCommandList: TheiaImportCommand[]): Promise { if (cloneCommandList.length === 0) { return; From 24d94187a5a68562ccd8699f0eee0b956ceb28cd Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Thu, 17 Dec 2020 16:31:22 +0200 Subject: [PATCH 06/10] Add workspace folders in turn Signed-off-by: Roman Nikitenko --- .../src/workspace-folder-updater.ts | 85 +++++++++++++++++++ .../src/workspace-projects-manager.ts | 29 ++----- 2 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 plugins/workspace-plugin/src/workspace-folder-updater.ts diff --git a/plugins/workspace-plugin/src/workspace-folder-updater.ts b/plugins/workspace-plugin/src/workspace-folder-updater.ts new file mode 100644 index 000000000..0e9ecbf70 --- /dev/null +++ b/plugins/workspace-plugin/src/workspace-folder-updater.ts @@ -0,0 +1,85 @@ +/********************************************************************** + * Copyright (c) 2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import * as theia from '@theia/plugin'; +const UPDATE_WORKSPACE_FOLDER_TIMEOUT = 5000; + +export class WorkspaceFolderUpdater { + private pendingFolders: string[] = []; + private addingWorkspaceFolderPromise: Promise | undefined; + + async addWorkspaceFolder(path: string): Promise { + const workspaceFolderPath = this.toValidWorkspaceFolderPath(path); + if (this.pendingFolders.includes(workspaceFolderPath)) { + return Promise.resolve(); + } + + if (this.addingWorkspaceFolderPromise) { + this.pendingFolders.push(workspaceFolderPath); + } else { + try { + this.addingWorkspaceFolderPromise = this.addFolder(workspaceFolderPath); + await this.addingWorkspaceFolderPromise; + } catch (error) { + console.error(error); + } finally { + this.addingWorkspaceFolderPromise = undefined; + } + + const next = this.pendingFolders.shift(); + if (next) { + this.addWorkspaceFolder(next); + } + } + return Promise.resolve(); + } + + protected addFolder(projectPath: string): Promise { + const isProjectFolder = (folder: theia.WorkspaceFolder) => folder.uri.path === projectPath; + const workspaceFolders = theia.workspace.workspaceFolders || []; + if (workspaceFolders.some(isProjectFolder)) { + return Promise.resolve(undefined); + } + + return new Promise((resolve, reject) => { + const disposable = theia.workspace.onDidChangeWorkspaceFolders(event => { + const existingWorkspaceFolders = theia.workspace.workspaceFolders || []; + if (event.added.some(isProjectFolder) || existingWorkspaceFolders.some(isProjectFolder)) { + clearTimeout(addFolderTimeout); + + disposable.dispose(); + + resolve(); + } + }); + + const addFolderTimeout = setTimeout(() => { + disposable.dispose(); + + reject( + new Error( + `Adding workspace folder ${projectPath} was cancelled by timeout ${UPDATE_WORKSPACE_FOLDER_TIMEOUT} ms` + ) + ); + }, UPDATE_WORKSPACE_FOLDER_TIMEOUT); + + theia.workspace.updateWorkspaceFolders(workspaceFolders ? workspaceFolders.length : 0, undefined, { + uri: theia.Uri.file(projectPath), + }); + }); + } + + protected toValidWorkspaceFolderPath(path: string): string { + if (path.endsWith('/')) { + return path.slice(0, -1); + } + return path; + } +} diff --git a/plugins/workspace-plugin/src/workspace-projects-manager.ts b/plugins/workspace-plugin/src/workspace-projects-manager.ts index 2f1ca6c75..39a9f268b 100644 --- a/plugins/workspace-plugin/src/workspace-projects-manager.ts +++ b/plugins/workspace-plugin/src/workspace-projects-manager.ts @@ -18,6 +18,7 @@ import * as theia from '@theia/plugin'; import { TheiaImportCommand, buildProjectImportCommand } from './theia-commands'; +import { WorkspaceFolderUpdater } from './workspace-folder-updater'; import { che as cheApi } from '@eclipse-che/api'; const onDidCloneSourcesEmitter = new theia.EventEmitter(); @@ -30,7 +31,8 @@ export function handleWorkspaceProjects(pluginContext: theia.PluginContext, proj } export class WorkspaceProjectsManager { - watchers: theia.FileSystemWatcher[] = []; + protected watchers: theia.FileSystemWatcher[] = []; + protected workspaceFolderUpdater = new WorkspaceFolderUpdater(); constructor(protected pluginContext: theia.PluginContext, protected projectsRoot: string) {} @@ -73,23 +75,7 @@ export class WorkspaceProjectsManager { this.getProjects(workspace) .map(project => this.getProjectPath(project)) .filter(projectPath => fs.existsSync(projectPath)) - .forEach(projectPath => this.addWorkspaceFolder(projectPath)); - } - - addWorkspaceFolder(projectPath: string): void { - const workspaceFolders = theia.workspace.workspaceFolders; - if (workspaceFolders && workspaceFolders.length > 0) { - const pathsList = workspaceFolders.map(folder => folder.uri.path); - if (pathsList.indexOf(projectPath) === -1) { - theia.workspace.updateWorkspaceFolders(workspaceFolders ? workspaceFolders.length : 0, undefined, { - uri: theia.Uri.file(projectPath), - }); - } - } else { - theia.workspace.updateWorkspaceFolders(0, undefined, { - uri: theia.Uri.file(projectPath), - }); - } + .forEach(projectPath => this.workspaceFolderUpdater.addWorkspaceFolder(projectPath)); } private async executeCloneCommands(cloneCommandList: TheiaImportCommand[]): Promise { @@ -105,12 +91,7 @@ export class WorkspaceProjectsManager { cloningPromises.push(cloningPromise); - cloningPromise.then(() => { - const workspaceFolders = theia.workspace.workspaceFolders; - theia.workspace.updateWorkspaceFolders(workspaceFolders ? workspaceFolders.length : 0, undefined, { - uri: theia.Uri.file(cloneCommand.getProjectPath()), - }); - }); + cloningPromise.then(() => this.workspaceFolderUpdater.addWorkspaceFolder(cloneCommand.getProjectPath())); } await Promise.all(cloningPromises); From 7f6ffdf6de9be4ab65aef583500f5ffbdc22c93a Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Wed, 23 Dec 2020 17:41:15 +0200 Subject: [PATCH 07/10] Use 'onDidChangeWorkspaceFolders' event to handle readme files Signed-off-by: Roman Nikitenko --- plugins/welcome-plugin/src/welcome-plugin.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/plugins/welcome-plugin/src/welcome-plugin.ts b/plugins/welcome-plugin/src/welcome-plugin.ts index 7dd80a760..f429db56e 100644 --- a/plugins/welcome-plugin/src/welcome-plugin.ts +++ b/plugins/welcome-plugin/src/welcome-plugin.ts @@ -53,8 +53,7 @@ async function getHtmlForWebview(context: theia.PluginContext): Promise } // Open Readme file is there is one -export async function handleReadmeFiles(): Promise { - const roots: theia.WorkspaceFolder[] | undefined = theia.workspace.workspaceFolders; +export async function handleReadmeFiles(roots: theia.WorkspaceFolder[]): Promise { // In case of only one workspace if (roots && roots.length === 1) { const children = await theia.workspace.findFiles('README.md', 'node_modules/**', 1); @@ -126,12 +125,9 @@ export function start(context: theia.PluginContext): void { setTimeout(async () => { addPanel(context); - const workspacePlugin = theia.plugins.getPlugin('Eclipse Che.@eclipse-che/workspace-plugin'); - if (workspacePlugin) { - workspacePlugin.exports.onDidCloneSources(() => handleReadmeFiles()); - } else { - handleReadmeFiles(); - } + theia.workspace.onDidChangeWorkspaceFolders(event => { + handleReadmeFiles(event.added); + }, context.subscriptions); }, 100); } } From 96592159ed89d5644367be23b29390d637d7df24 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Fri, 15 Jan 2021 10:35:01 +0200 Subject: [PATCH 08/10] Fix: file watcher should fire an event at launch.json file creation Signed-off-by: Roman Nikitenko --- plugins/task-plugin/package.json | 1 + .../src/export/launch-configs-exporter.ts | 43 +++++++++++++------ .../src/export/task-configs-exporter.ts | 20 +++++---- .../config-file-launch-configs-extractor.ts | 6 +-- .../config-file-task-configs-extractor.ts | 6 +-- plugins/task-plugin/src/utils.ts | 31 ++++++++++++- 6 files changed, 78 insertions(+), 29 deletions(-) diff --git a/plugins/task-plugin/package.json b/plugins/task-plugin/package.json index d397dbdda..481bf622b 100644 --- a/plugins/task-plugin/package.json +++ b/plugins/task-plugin/package.json @@ -70,6 +70,7 @@ "vscode-uri": "2.1.1", "vscode-ws-jsonrpc": "0.2.0", "ws": "^5.2.2", + "fs-extra": "^8.1.0", "jsonc-parser": "^2.0.2" } } diff --git a/plugins/task-plugin/src/export/launch-configs-exporter.ts b/plugins/task-plugin/src/export/launch-configs-exporter.ts index 39b407ea3..a416e1a4b 100644 --- a/plugins/task-plugin/src/export/launch-configs-exporter.ts +++ b/plugins/task-plugin/src/export/launch-configs-exporter.ts @@ -10,8 +10,8 @@ import * as theia from '@theia/plugin'; +import { ensureDirExists, modify, writeFile } from '../utils'; import { inject, injectable } from 'inversify'; -import { modify, writeFileSync } from '../utils'; import { ConfigFileLaunchConfigsExtractor } from '../extract/config-file-launch-configs-extractor'; import { ConfigurationsExporter } from './export-configs-manager'; @@ -46,23 +46,23 @@ export class LaunchConfigurationsExporter implements ConfigurationsExporter { } async doExport(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): Promise { - const launchConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path); - const configFileConfigs = this.configFileLaunchConfigsExtractor.extract(launchConfigFileUri); + const workspaceFolderPath = workspaceFolder.uri.path; + const launchConfigFilePath = resolve(workspaceFolderPath, CONFIG_DIR, LAUNCH_CONFIG_FILE); + const configFileConfigs = await this.configFileLaunchConfigsExtractor.extract(launchConfigFilePath); const vsCodeConfigs = this.vsCodeLaunchConfigsExtractor.extract(commands); const configFileContent = configFileConfigs.content; if (configFileContent) { - this.saveConfigs( - launchConfigFileUri, + return this.saveConfigs( + workspaceFolderPath, configFileContent, this.merge(configFileConfigs.configs, vsCodeConfigs.configs, this.getConsoleConflictLogger()) ); - return; } const vsCodeConfigsContent = vsCodeConfigs.content; if (vsCodeConfigsContent) { - this.saveConfigs(launchConfigFileUri, vsCodeConfigsContent, vsCodeConfigs.configs); + return this.saveConfigs(workspaceFolderPath, vsCodeConfigsContent, vsCodeConfigs.configs); } } @@ -100,13 +100,32 @@ export class LaunchConfigurationsExporter implements ConfigurationsExporter { return JSON.stringify(properties1) === JSON.stringify(properties2); } - private getConfigFileUri(rootDir: string): string { - return resolve(rootDir.toString(), CONFIG_DIR, LAUNCH_CONFIG_FILE); - } + private async saveConfigs( + workspaceFolderPath: string, + content: string, + configurations: theia.DebugConfiguration[] + ): Promise { + /* + There is an issue related to file watchers: the watcher only reports the first directory when creating recursively directories. + For example: + - we would like to create /projects/someProject/.theia/launch.json recursively + - /projects/someProject directory already exists + - .theia directory and launch.json file should be created + - as result file watcher fires an event that .theia directory was created, there is no an event about launch.json file + + The issue is reproduced not permanently. + + We had to use the workaround to avoid the issue: first we create the directory and then - config file + */ + + const configDirPath = resolve(workspaceFolderPath, CONFIG_DIR); + await ensureDirExists(configDirPath); + + const launchConfigFilePath = resolve(configDirPath, LAUNCH_CONFIG_FILE); + await ensureDirExists(launchConfigFilePath); - private saveConfigs(launchConfigFileUri: string, content: string, configurations: theia.DebugConfiguration[]): void { const result = modify(content, ['configurations'], configurations, formattingOptions); - writeFileSync(launchConfigFileUri, result); + return writeFile(launchConfigFilePath, result); } private getConsoleConflictLogger(): (config1: theia.DebugConfiguration, config2: theia.DebugConfiguration) => void { diff --git a/plugins/task-plugin/src/export/task-configs-exporter.ts b/plugins/task-plugin/src/export/task-configs-exporter.ts index 370dcb7ad..104820212 100644 --- a/plugins/task-plugin/src/export/task-configs-exporter.ts +++ b/plugins/task-plugin/src/export/task-configs-exporter.ts @@ -11,7 +11,7 @@ import * as startPoint from '../task-plugin-backend'; import { inject, injectable } from 'inversify'; -import { modify, writeFileSync } from '../utils'; +import { modify, writeFile } from '../utils'; import { BackwardCompatibilityResolver } from '../task/backward-compatibility'; import { CheTaskConfigsExtractor } from '../extract/che-task-configs-extractor'; @@ -49,7 +49,7 @@ export class TaskConfigurationsExporter implements ConfigurationsExporter { protected readonly backwardCompatibilityResolver: BackwardCompatibilityResolver; async export(commands: cheApi.workspace.Command[]): Promise { - const configFileTasks = this.configFileTasksExtractor.extract(THEIA_USER_TASKS_PATH); + const configFileTasks = await this.configFileTasksExtractor.extract(THEIA_USER_TASKS_PATH); const cheTasks = this.cheTaskConfigsExtractor.extract(commands); const vsCodeTasks = this.vsCodeTaskConfigsExtractor.extract(commands); @@ -58,22 +58,20 @@ export class TaskConfigurationsExporter implements ConfigurationsExporter { const configFileContent = configFileTasks.content; if (configFileContent) { - this.saveConfigs( + return this.saveConfigs( THEIA_USER_TASKS_PATH, configFileContent, this.merge(configFileConfigs, devfileConfigs, this.getConsoleConflictLogger()) ); - return; } const vsCodeTasksContent = vsCodeTasks.content; if (vsCodeTasksContent) { - this.saveConfigs(THEIA_USER_TASKS_PATH, vsCodeTasksContent, devfileConfigs); - return; + return this.saveConfigs(THEIA_USER_TASKS_PATH, vsCodeTasksContent, devfileConfigs); } if (cheTasks) { - this.saveConfigs(THEIA_USER_TASKS_PATH, '', cheTasks); + return this.saveConfigs(THEIA_USER_TASKS_PATH, '', cheTasks); } } @@ -112,14 +110,18 @@ export class TaskConfigurationsExporter implements ConfigurationsExporter { return JSON.stringify(properties1) === JSON.stringify(properties2); } - private saveConfigs(tasksConfigFileUri: string, content: string, configurations: TaskConfiguration[]): void { + private async saveConfigs( + tasksConfigFileUri: string, + content: string, + configurations: TaskConfiguration[] + ): Promise { const result = modify( content, ['tasks'], configurations.map(config => Object.assign(config, { _scope: undefined })), formattingOptions ); - writeFileSync(tasksConfigFileUri, result); + return writeFile(tasksConfigFileUri, result); } private getOutputChannelConflictLogger(): (config1: TaskConfiguration, config2: TaskConfiguration) => void { diff --git a/plugins/task-plugin/src/extract/config-file-launch-configs-extractor.ts b/plugins/task-plugin/src/extract/config-file-launch-configs-extractor.ts index 6aa8bff3d..83631182c 100644 --- a/plugins/task-plugin/src/extract/config-file-launch-configs-extractor.ts +++ b/plugins/task-plugin/src/extract/config-file-launch-configs-extractor.ts @@ -10,7 +10,7 @@ import * as theia from '@theia/plugin'; -import { parse, readFileSync } from '../utils'; +import { parse, readFile } from '../utils'; import { Configurations } from '../export/export-configs-manager'; import { injectable } from 'inversify'; @@ -18,8 +18,8 @@ import { injectable } from 'inversify'; /** Extracts launch configurations from config file by given uri. */ @injectable() export class ConfigFileLaunchConfigsExtractor { - extract(launchConfigFileUri: string): Configurations { - const configsContent = readFileSync(launchConfigFileUri); + async extract(launchConfigFileUri: string): Promise> { + const configsContent = await readFile(launchConfigFileUri); const configsJson = parse(configsContent); if (!configsJson || !configsJson.configurations) { return { content: '', configs: [] }; diff --git a/plugins/task-plugin/src/extract/config-file-task-configs-extractor.ts b/plugins/task-plugin/src/extract/config-file-task-configs-extractor.ts index 21bf2419c..5ce6d4d87 100644 --- a/plugins/task-plugin/src/extract/config-file-task-configs-extractor.ts +++ b/plugins/task-plugin/src/extract/config-file-task-configs-extractor.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ -import { parse, readFileSync } from '../utils'; +import { parse, readFile } from '../utils'; import { Configurations } from '../export/export-configs-manager'; import { TaskConfiguration } from '@eclipse-che/plugin'; @@ -17,8 +17,8 @@ import { injectable } from 'inversify'; /** Extracts configurations of tasks from config file by given uri. */ @injectable() export class ConfigFileTasksExtractor { - extract(tasksConfigFileUri: string): Configurations { - const tasksContent = readFileSync(tasksConfigFileUri); + async extract(tasksConfigFileUri: string): Promise> { + const tasksContent = await readFile(tasksConfigFileUri); const tasksJson = parse(tasksContent); if (!tasksJson || !tasksJson.tasks) { return { content: '', configs: [] }; diff --git a/plugins/task-plugin/src/utils.ts b/plugins/task-plugin/src/utils.ts index 40097e0ca..5ad9f4b4d 100644 --- a/plugins/task-plugin/src/utils.ts +++ b/plugins/task-plugin/src/utils.ts @@ -8,6 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ +import * as fs from 'fs-extra'; import * as jsoncparser from 'jsonc-parser'; import * as path from 'path'; @@ -16,8 +17,6 @@ import { FormattingOptions, JSONPath, ParseError } from 'jsonc-parser'; import { URL } from 'url'; import { resolve } from 'path'; -const fs = require('fs'); - /** Allows to get attribute by given name, returns `undefined` if attribute is not found */ export function getAttribute(attributeName: string, attributes?: { [key: string]: string }): string | undefined { if (!attributes) { @@ -102,12 +101,31 @@ export function readFileSync(filePath: string): string { } } +/** Asynchronously reads the file by given path. Returns content of the file or empty string if file doesn't exist */ +export async function readFile(filePath: string): Promise { + try { + if (await fs.pathExists(filePath)) { + return fs.readFile(filePath, 'utf8'); + } + return ''; + } catch (e) { + console.error(e); + return ''; + } +} + /** Synchronously writes given content to the file. Creates directories to the file if they don't exist */ export function writeFileSync(filePath: string, content: string): void { ensureDirExistence(filePath); fs.writeFileSync(filePath, content); } +/** Asynchronously writes given content to the file. Creates directories to the file if they don't exist */ +export async function writeFile(filePath: string, content: string): Promise { + await ensureDirExists(filePath); + return fs.writeFile(filePath, content); +} + /** Synchronously creates a directory to the file if they don't exist */ export function ensureDirExistence(filePath: string): void { const dirName = path.dirname(filePath); @@ -116,3 +134,12 @@ export function ensureDirExistence(filePath: string): void { } fs.mkdirSync(dirName, { recursive: true }); } + +/** Creates a directory containing the file if they don't exist */ +export async function ensureDirExists(filePath: string): Promise { + const dirName = path.dirname(filePath); + if (await fs.pathExists(dirName)) { + return; + } + return fs.mkdirp(dirName); +} From 058aaad73450eeb91cb1d97b80561f3a3b06325e Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Mon, 8 Feb 2021 11:57:37 +0200 Subject: [PATCH 09/10] Fix 'Open Configurations' and 'Add configuration' actions Signed-off-by: Roman Nikitenko --- .../che-debug-configuration-manager.ts | 32 +++++++++++++++++++ .../src/browser/che-frontend-module.ts | 8 +++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 extensions/eclipse-che-theia-plugin-ext/src/browser/che-debug-configuration-manager.ts diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-debug-configuration-manager.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-debug-configuration-manager.ts new file mode 100644 index 000000000..00bc99e9f --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-debug-configuration-manager.ts @@ -0,0 +1,32 @@ +/********************************************************************** + * Copyright (c) 2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import { injectable, postConstruct } from 'inversify'; + +import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; + +@injectable() +export class CheDebugConfigurationManager extends DebugConfigurationManager { + @postConstruct() + protected async init(): Promise { + super.init(); + + /** + * Theia creates a DebugConfigurationModel for each workspace folder in a workspace at starting the IDE. + * For the CHE multi-root workspace there no workspace folders at that step: + * CHE clones projects at starting the IDE and adds a workspace folder directly after cloning a project. + * That's why we need the following logic - + * DebugConfigurationManager should create the corresponding model when a workspace is changed (a workspace folder is added) + */ + this.workspaceService.onWorkspaceChanged(() => { + this.updateModels(); + }); + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts index 4e33bcf3d..aa9fa0d62 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts @@ -22,9 +22,11 @@ import { } from '../common/che-protocol'; import { CheSideCarContentReaderRegistryImpl, CheSideCarResourceResolver } from './che-sidecar-resource'; import { CommandContribution, ResourceResolver } from '@theia/core/lib/common'; +import { ContainerModule, interfaces } from 'inversify'; import { WebSocketConnectionProvider, WidgetFactory } from '@theia/core/lib/browser'; import { CheApiProvider } from './che-api-provider'; +import { CheDebugConfigurationManager } from './che-debug-configuration-manager'; import { CheLanguagesMainTestImpl } from './che-languages-test-main'; import { ChePluginCommandContribution } from './plugin/che-plugin-command-contribution'; import { ChePluginFrontentService } from './plugin/che-plugin-frontend-service'; @@ -38,8 +40,8 @@ import { CheTaskClientImpl } from './che-task-client'; import { CheTaskResolver } from './che-task-resolver'; import { CheTaskTerminalWidgetManager } from './che-task-terminal-widget-manager'; import { CheWebviewEnvironment } from './che-webview-environment'; -import { ContainerModule } from 'inversify'; import { ContainerPicker } from './container-picker'; +import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { LanguagesMainFactory } from '@theia/plugin-ext'; import { MainPluginApiProvider } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution'; import { PluginFrontendViewContribution } from '@theia/plugin-ext/lib/main/browser/plugin-frontend-view-contribution'; @@ -50,7 +52,6 @@ import { TaskStatusHandler } from './task-status-handler'; import { TaskTerminalWidgetManager } from '@theia/task/lib/browser/task-terminal-widget-manager'; import { WebviewEnvironment } from '@theia/plugin-ext/lib/main/browser/webview/webview-environment'; import { bindChePluginPreferences } from './plugin/che-plugin-preferences'; -import { interfaces } from 'inversify'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(CheApiProvider).toSelf().inSingletonScope(); @@ -124,4 +125,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { child.bind(RPCProtocol).toConstantValue(rpc); return child.get(CheLanguagesMainTestImpl); }); + + bind(CheDebugConfigurationManager).toSelf().inSingletonScope(); + rebind(DebugConfigurationManager).to(CheDebugConfigurationManager).inSingletonScope(); }); From dee2d7374e32a468a24f837087ac4dda2cf74784 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Thu, 11 Feb 2021 17:40:28 +0200 Subject: [PATCH 10/10] An ability to switch (turn on and turn off ) multi-root mode on a devfile level Signed-off-by: Roman Nikitenko --- .../src/node/che-workspace-server.ts | 40 ++++----- .../src/che-task-backend-module.ts | 3 +- .../src/export/export-configs-manager.ts | 29 +++---- .../src/export/launch-configs-exporter.ts | 23 ++++- plugins/welcome-plugin/src/welcome-plugin.ts | 86 +++++++++++++------ .../workspace-plugin/src/theia-commands.ts | 31 +++---- .../src/workspace-folder-updater.ts | 2 +- .../src/workspace-projects-manager.ts | 44 ++++++---- 8 files changed, 152 insertions(+), 106 deletions(-) diff --git a/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts index ab765bf30..e9e8506f0 100644 --- a/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts +++ b/extensions/eclipse-che-theia-workspace/src/node/che-workspace-server.ts @@ -11,11 +11,11 @@ import * as fs from 'fs-extra'; import * as path from 'path'; +import { Workspace, WorkspaceService } from '@eclipse-che/theia-remote-api/lib/common/workspace-service'; import { inject, injectable } from 'inversify'; import { DefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server'; import { FileUri } from '@theia/core/lib/node'; -import { WorkspaceService } from '@eclipse-che/theia-remote-api/lib/common/workspace-service'; interface TheiaWorkspace { folders: TheiaWorkspacePath[]; @@ -33,33 +33,29 @@ export class CheWorkspaceServer extends DefaultWorkspaceServer { // override any workspace that could have been defined through CLI and use entries from the devfile // if not possible, use default method protected async getRoot(): Promise { - let projectsRoot: string; - if (process.env.CHE_PROJECTS_ROOT) { - projectsRoot = process.env.CHE_PROJECTS_ROOT; - } else { - projectsRoot = '/projects'; + const workspace = await this.workspaceService.currentWorkspace(); + if (!isMultiRoot(workspace)) { + return super.getRoot(); } + const projectsRootEnvVariable = process.env.CHE_PROJECTS_ROOT; + const projectsRoot = projectsRootEnvVariable ? projectsRootEnvVariable : '/projects'; + // first, check if we have a che.theia-workspace file const cheTheiaWorkspaceFile = path.resolve(projectsRoot, 'che.theia-workspace'); + const cheTheiaWorkspaceFileUri = FileUri.create(cheTheiaWorkspaceFile); const exists = await fs.pathExists(cheTheiaWorkspaceFile); - if (exists) { - return FileUri.create(cheTheiaWorkspaceFile).toString(); - } - - // no, then create the file - - const workspace = await this.workspaceService.currentWorkspace(); - const devfile = workspace.devfile; - if (devfile) { - const projects = devfile.projects; - if (projects) { - const theiaWorkspace: TheiaWorkspace = { folders: [] }; - await fs.writeFile(cheTheiaWorkspaceFile, JSON.stringify(theiaWorkspace), { encoding: 'utf8' }); - return FileUri.create(cheTheiaWorkspaceFile).toString(); - } + if (!exists) { + // no, then create the file + const theiaWorkspace: TheiaWorkspace = { folders: [] }; + await fs.writeFile(cheTheiaWorkspaceFile, JSON.stringify(theiaWorkspace), { encoding: 'utf8' }); } - return super.getRoot(); + return cheTheiaWorkspaceFileUri.toString(); } } + +function isMultiRoot(workspace: Workspace): boolean { + const devfile = workspace.devfile; + return !!devfile && !!devfile.attributes && !!devfile.attributes.multiRoot && devfile.attributes.multiRoot === 'on'; +} diff --git a/plugins/task-plugin/src/che-task-backend-module.ts b/plugins/task-plugin/src/che-task-backend-module.ts index 1a592d70d..b964bcc77 100644 --- a/plugins/task-plugin/src/che-task-backend-module.ts +++ b/plugins/task-plugin/src/che-task-backend-module.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2019-2020 Red Hat, Inc. + * Copyright (c) 2019-2021 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -49,6 +49,7 @@ container.bind(ProjectPathVariableResolver).toSelf().inSingletonScope(); container.bind(CheWorkspaceClient).toSelf().inSingletonScope(); container.bind(CheTaskPreviewMode).toSelf().inSingletonScope(); container.bind(PreviewUrlOpenService).toSelf().inSingletonScope(); +container.bind(LaunchConfigurationsExporter).toSelf().inSingletonScope(); container.bind(ConfigurationsExporter).to(TaskConfigurationsExporter).inSingletonScope(); container.bind(ConfigurationsExporter).to(LaunchConfigurationsExporter).inSingletonScope(); container.bind(ExportConfigurationsManager).toSelf().inSingletonScope(); diff --git a/plugins/task-plugin/src/export/export-configs-manager.ts b/plugins/task-plugin/src/export/export-configs-manager.ts index 5b5b5b375..9021f6f41 100644 --- a/plugins/task-plugin/src/export/export-configs-manager.ts +++ b/plugins/task-plugin/src/export/export-configs-manager.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2019-2020 Red Hat, Inc. + * Copyright (c) 2019-2021 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -8,12 +8,10 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ -import * as startPoint from '../task-plugin-backend'; -import * as theia from '@theia/plugin'; - import { inject, injectable, multiInject } from 'inversify'; import { CheWorkspaceClient } from '../che-workspace-client'; +import { LaunchConfigurationsExporter } from './launch-configs-exporter'; import { che as cheApi } from '@eclipse-che/api'; export const ConfigurationsExporter = Symbol('ConfigurationsExporter'); @@ -45,26 +43,21 @@ export class ExportConfigurationsManager { @multiInject(ConfigurationsExporter) protected readonly exporters: ConfigurationsExporter[]; - init(): void { - this.export(); + @inject(LaunchConfigurationsExporter) + protected readonly launchConfigurationsExporter: LaunchConfigurationsExporter; - theia.workspace.onDidChangeWorkspaceFolders( - event => { - const workspaceFolders = event.added; - if (workspaceFolders && workspaceFolders.length > 0) { - this.export(); - } - }, - undefined, - startPoint.getSubscriptions() - ); + protected cheCommands: cheApi.workspace.Command[] = []; + + async init(): Promise { + this.cheCommands = await this.cheWorkspaceClient.getCommands(); + this.launchConfigurationsExporter.init(this.cheCommands); + this.export(); } async export(): Promise { const exportPromises = []; - const cheCommands = await this.cheWorkspaceClient.getCommands(); for (const exporter of this.exporters) { - exportPromises.push(this.doExport(cheCommands, exporter)); + exportPromises.push(this.doExport(this.cheCommands, exporter)); } await Promise.all(exportPromises); diff --git a/plugins/task-plugin/src/export/launch-configs-exporter.ts b/plugins/task-plugin/src/export/launch-configs-exporter.ts index a416e1a4b..70b5abfcb 100644 --- a/plugins/task-plugin/src/export/launch-configs-exporter.ts +++ b/plugins/task-plugin/src/export/launch-configs-exporter.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2019-2020 Red Hat, Inc. + * Copyright (c) 2019-2021 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -8,6 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ +import * as startPoint from '../task-plugin-backend'; import * as theia from '@theia/plugin'; import { ensureDirExists, modify, writeFile } from '../utils'; @@ -32,14 +33,28 @@ export class LaunchConfigurationsExporter implements ConfigurationsExporter { @inject(VsCodeLaunchConfigsExtractor) protected readonly vsCodeLaunchConfigsExtractor: VsCodeLaunchConfigsExtractor; - async export(commands: cheApi.workspace.Command[]): Promise { - if (!theia.workspace.workspaceFolders) { + async init(commands: cheApi.workspace.Command[]): Promise { + theia.workspace.onDidChangeWorkspaceFolders( + event => { + const workspaceFolders: theia.WorkspaceFolder[] | undefined = event.added; + if (workspaceFolders && workspaceFolders.length > 0) { + this.export(commands, workspaceFolders); + } + }, + undefined, + startPoint.getSubscriptions() + ); + } + + async export(commands: cheApi.workspace.Command[], workspaceFolders?: theia.WorkspaceFolder[]): Promise { + workspaceFolders = workspaceFolders ? workspaceFolders : theia.workspace.workspaceFolders; + if (!workspaceFolders) { return; } const exportConfigsPromises: Promise[] = []; - for (const workspaceFolder of theia.workspace.workspaceFolders) { + for (const workspaceFolder of workspaceFolders) { exportConfigsPromises.push(this.doExport(workspaceFolder, commands)); } await Promise.all(exportConfigsPromises); diff --git a/plugins/welcome-plugin/src/welcome-plugin.ts b/plugins/welcome-plugin/src/welcome-plugin.ts index f429db56e..f937babd8 100644 --- a/plugins/welcome-plugin/src/welcome-plugin.ts +++ b/plugins/welcome-plugin/src/welcome-plugin.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2020 Red Hat, Inc. + * Copyright (c) 2020-2021 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -53,22 +53,33 @@ async function getHtmlForWebview(context: theia.PluginContext): Promise } // Open Readme file is there is one -export async function handleReadmeFiles(roots: theia.WorkspaceFolder[]): Promise { - // In case of only one workspace - if (roots && roots.length === 1) { - const children = await theia.workspace.findFiles('README.md', 'node_modules/**', 1); - const updatedChildren = children.filter((child: theia.Uri) => { - if (child.fsPath.indexOf('node_modules') === -1) { - return child; - } - }); +export async function handleReadmeFiles( + readmeHandledCallback?: () => void, + roots?: theia.WorkspaceFolder[] +): Promise { + roots = roots ? roots : theia.workspace.workspaceFolders; + if (!roots || roots.length < 1) { + return; + } - if (updatedChildren.length >= 1) { - const openPath = theia.Uri.parse(updatedChildren[0] + '?open-handler=code-editor-preview'); - const doc: theia.TextDocument | undefined = await theia.workspace.openTextDocument(openPath); - if (doc) { - theia.window.showTextDocument(doc); - } + const children = await theia.workspace.findFiles('README.md', 'node_modules/**', 1); + const updatedChildren = children.filter((child: theia.Uri) => { + if (child.fsPath.indexOf('node_modules') === -1) { + return child; + } + }); + + if (updatedChildren.length < 1) { + return; + } + + const openPath = theia.Uri.parse(updatedChildren[0] + '?open-handler=code-editor-preview'); + const doc: theia.TextDocument | undefined = await theia.workspace.openTextDocument(openPath); + if (doc) { + theia.window.showTextDocument(doc); + + if (readmeHandledCallback) { + readmeHandledCallback(); } } } @@ -121,15 +132,42 @@ export function start(context: theia.PluginContext): void { showWelcomePage = configuration.get(Settings.SHOW_WELCOME_PAGE); } - if (showWelcomePage && theia.window.visibleTextEditors.length === 0) { - setTimeout(async () => { - addPanel(context); - - theia.workspace.onDidChangeWorkspaceFolders(event => { - handleReadmeFiles(event.added); - }, context.subscriptions); - }, 100); + if (!showWelcomePage || theia.window.visibleTextEditors.length > 0) { + return; } + + let cloneSourcesDisposable: theia.Disposable | undefined = undefined; + setTimeout(async () => { + addPanel(context); + + const workspacePlugin = theia.plugins.getPlugin('Eclipse Che.@eclipse-che/workspace-plugin'); + if (workspacePlugin && workspacePlugin.exports) { + // it handles the case when the multi-root mode is OFF + // we should remove this logic when we switch to the multi-root mode is ON by default + cloneSourcesDisposable = workspacePlugin.exports.onDidCloneSources( + () => handleReadmeFiles(readmeHandledCallback), + undefined, + context.subscriptions + ); + } else { + handleReadmeFiles(); + } + }, 100); + + // handles the case when the multi-root mode is ON + const changeWorkspaceFoldersDisposable = theia.workspace.onDidChangeWorkspaceFolders( + event => handleReadmeFiles(readmeHandledCallback, event.added), + undefined, + context.subscriptions + ); + + const readmeHandledCallback = () => { + changeWorkspaceFoldersDisposable.dispose(); + + if (cloneSourcesDisposable) { + cloneSourcesDisposable.dispose(); + } + }; } export function stop(): void {} diff --git a/plugins/workspace-plugin/src/theia-commands.ts b/plugins/workspace-plugin/src/theia-commands.ts index 052ab779a..1f2ff5d17 100644 --- a/plugins/workspace-plugin/src/theia-commands.ts +++ b/plugins/workspace-plugin/src/theia-commands.ts @@ -44,8 +44,8 @@ function isDevfileProjectConfig( } export interface TheiaImportCommand { - execute(): Promise; - getProjectPath(): string; + /** @returns the path to the imported project */ + execute(): Promise; } export function buildProjectImportCommand( @@ -120,11 +120,7 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { this.projectsRoot = projectsRoot; } - getProjectPath(): string { - return this.projectPath; - } - - clone(): PromiseLike { + clone(): PromiseLike { return theia.window.withProgress( { location: theia.ProgressLocation.Notification, @@ -140,7 +136,7 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { ); } - async execute(): Promise { + async execute(): Promise { if (!git.isSecureGitURI(this.locationURI)) { // clone using regular URI return this.clone(); @@ -208,7 +204,7 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { continue; } // skip - return; + return Promise.reject(new Error(message)); } // pause will be removed after debugging this method @@ -228,7 +224,7 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { private async gitClone( progress: theia.Progress<{ message?: string; increment?: number }>, token: theia.CancellationToken - ): Promise { + ): Promise { const args: string[] = ['clone', this.locationURI, this.projectPath]; if (this.checkoutBranch) { args.push('--branch'); @@ -265,9 +261,11 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { } else { theia.window.showInformationMessage(`${messageStart}.`); } + return this.projectPath; } catch (e) { theia.window.showErrorMessage(`Couldn't clone ${this.locationURI}: ${e.message}`); console.log(`Couldn't clone ${this.locationURI}`, e); + throw new Error(e); } } @@ -275,7 +273,7 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { private async gitSparseCheckout( progress: theia.Progress<{ message?: string; increment?: number }>, token: theia.CancellationToken - ): Promise { + ): Promise { if (!this.sparseCheckoutDir) { throw new Error('Parameter "sparseCheckoutDir" is not set for "' + this.projectName + '" project.'); } @@ -296,6 +294,7 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { theia.window.showInformationMessage( `Sources by template ${this.sparseCheckoutDir} of ${this.locationURI} was cloned to ${this.projectPath}.` ); + return this.projectPath; } } @@ -321,15 +320,11 @@ export class TheiaImportZipCommand implements TheiaImportCommand { } } - getProjectPath(): string { - return this.projectDir; - } - - async execute(): Promise { + async execute(): Promise { const importZip = async ( progress: theia.Progress<{ message?: string; increment?: number }>, token: theia.CancellationToken - ): Promise => { + ): Promise => { try { // download const curlArgs = ['-sSL', '--output', this.zipfilePath]; @@ -354,9 +349,11 @@ export class TheiaImportZipCommand implements TheiaImportCommand { if (zipfileParentDir.indexOf(os.tmpdir() + path.sep) === 0) { fs.rmdirSync(zipfileParentDir); } + return this.projectDir; } catch (e) { theia.window.showErrorMessage(`Couldn't import ${this.locationURI}: ${e.message}`); console.error(`Couldn't import ${this.locationURI}`, e); + throw new Error(e); } }; diff --git a/plugins/workspace-plugin/src/workspace-folder-updater.ts b/plugins/workspace-plugin/src/workspace-folder-updater.ts index 0e9ecbf70..f61220b3a 100644 --- a/plugins/workspace-plugin/src/workspace-folder-updater.ts +++ b/plugins/workspace-plugin/src/workspace-folder-updater.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2020 Red Hat, Inc. + * Copyright (c) 2021 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 diff --git a/plugins/workspace-plugin/src/workspace-projects-manager.ts b/plugins/workspace-plugin/src/workspace-projects-manager.ts index 39a9f268b..3c345ee07 100644 --- a/plugins/workspace-plugin/src/workspace-projects-manager.ts +++ b/plugins/workspace-plugin/src/workspace-projects-manager.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (c) 2018-2020 Red Hat, Inc. + * Copyright (c) 2018-2021 Red Hat, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -51,13 +51,10 @@ export class WorkspaceProjectsManager { const workspace = await che.workspace.getCurrentWorkspace(); const cloneCommandList = await this.buildCloneCommands(workspace); - const cloningPromise = this.executeCloneCommands(cloneCommandList); + const cloningPromise = this.executeCloneCommands(cloneCommandList, workspace); theia.window.withProgress({ location: { viewId: 'explorer' } }, () => cloningPromise); - await cloningPromise; - this.ensureWorkspaceFolders(workspace); - await this.startSyncWorkspaceProjects(); } @@ -71,30 +68,34 @@ export class WorkspaceProjectsManager { .map(project => buildProjectImportCommand(project, instance.projectsRoot)!); } - ensureWorkspaceFolders(workspace: cheApi.workspace.Workspace): void { - this.getProjects(workspace) - .map(project => this.getProjectPath(project)) - .filter(projectPath => fs.existsSync(projectPath)) - .forEach(projectPath => this.workspaceFolderUpdater.addWorkspaceFolder(projectPath)); - } - - private async executeCloneCommands(cloneCommandList: TheiaImportCommand[]): Promise { + private async executeCloneCommands( + cloneCommandList: TheiaImportCommand[], + workspace: cheApi.workspace.Workspace + ): Promise { if (cloneCommandList.length === 0) { return; } theia.window.showInformationMessage('Che Workspace: Starting importing projects.'); - const cloningPromises: PromiseLike[] = []; - for (const cloneCommand of cloneCommandList) { - const cloningPromise = cloneCommand.execute(); - - cloningPromises.push(cloningPromise); + const isMultiRoot = isMultiRootWorkspace(workspace); - cloningPromise.then(() => this.workspaceFolderUpdater.addWorkspaceFolder(cloneCommand.getProjectPath())); + const cloningPromises: PromiseLike[] = []; + for (const cloneCommand of cloneCommandList) { + try { + const cloningPromise = cloneCommand.execute(); + cloningPromises.push(cloningPromise); + + if (isMultiRoot) { + cloningPromise.then(projectPath => this.workspaceFolderUpdater.addWorkspaceFolder(projectPath)); + } + } catch (e) { + // we continue to clone other projects even if a clone process failed for a project + } } await Promise.all(cloningPromises); + theia.window.showInformationMessage('Che Workspace: Finished importing projects.'); onDidCloneSourcesEmitter.fire(); } @@ -167,3 +168,8 @@ export class WorkspaceProjectsManager { ); } } + +function isMultiRootWorkspace(workspace: cheApi.workspace.Workspace): boolean { + const devfile = workspace.devfile; + return !!devfile && !!devfile.attributes && !!devfile.attributes.multiRoot && devfile.attributes.multiRoot === 'on'; +}