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 ab765bf304..e9e8506f04 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 1a592d70d8..b964bcc776 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 5b5b5b375b..9021f6f413 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 a416e1a4b9..70b5abfcb8 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 f429db56e3..c1046e3bcb 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,40 @@ 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); + if (!showWelcomePage || theia.window.visibleTextEditors.length > 0) { + return; + } - theia.workspace.onDidChangeWorkspaceFolders(event => { - handleReadmeFiles(event.added); - }, context.subscriptions); - }, 100); + addPanel(context); + + let cloneSourcesDisposable: theia.Disposable | undefined = undefined; + const workspacePlugin = theia.plugins.getPlugin('Eclipse Che.@eclipse-che/workspace-plugin'); + if (workspacePlugin) { + // 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(); } + + // 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 1b70490e54..535ade9179 100644 --- a/plugins/workspace-plugin/src/theia-commands.ts +++ b/plugins/workspace-plugin/src/theia-commands.ts @@ -43,8 +43,8 @@ function isDevfileProjectConfig( } export interface TheiaImportCommand { - execute(): PromiseLike; - getProjectPath(): string; + /** @returns the path to the imported project */ + execute(): PromiseLike; } export function buildProjectImportCommand( @@ -117,15 +117,11 @@ export class TheiaGitCloneCommand implements TheiaImportCommand { this.projectsRoot = projectsRoot; } - getProjectPath(): string { - return this.projectPath; - } - - execute(): PromiseLike { + execute(): PromiseLike { let cloneFunc: ( progress: theia.Progress<{ message?: string; increment?: number }>, token: theia.CancellationToken - ) => Promise; + ) => Promise; if (this.sparseCheckoutDir) { // Sparse checkout cloneFunc = this.gitSparseCheckout; @@ -147,7 +143,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'); @@ -184,9 +180,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); } } @@ -194,7 +192,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.'); } @@ -215,6 +213,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; } } @@ -240,15 +239,11 @@ export class TheiaImportZipCommand implements TheiaImportCommand { } } - getProjectPath(): string { - return this.projectDir; - } - - execute(): PromiseLike { + execute(): PromiseLike { const importZip = async ( progress: theia.Progress<{ message?: string; increment?: number }>, token: theia.CancellationToken - ): Promise => { + ): Promise => { try { // download const curlArgs = ['-sSL', '--output', this.zipfilePath]; @@ -273,9 +268,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 53f4cc0c7d..804efcf5a4 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 65b4cc82e0..9295502d2c 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 @@ -53,41 +53,40 @@ abstract class WorkspaceProjectsManager { workspace = await che.workspace.getCurrentWorkspace(); } - const cloneCommandList = await this.selectProjectToCloneCommands(workspace); - const cloningPromise = this.executeCloneCommands(cloneCommandList); + const cloningPromise = this.executeCloneCommands(workspace); theia.window.withProgress({ location: { viewId: 'explorer' } }, () => cloningPromise); - await cloningPromise; - - this.ensureWorkspaceFolders(workspace); + await cloningPromise; await this.startSyncWorkspaceProjects(); } - 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(workspace: cheApi.workspace.Workspace): Promise { + const cloneCommandList = await this.selectProjectToCloneCommands(workspace); 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(); } @@ -254,3 +253,8 @@ export class WorkspaceConfigProjectsManager extends WorkspaceProjectsManager { ); } } + +function isMultiRootWorkspace(workspace: cheApi.workspace.Workspace): boolean { + const devfile = workspace.devfile; + return !!devfile && !!devfile.attributes && !!devfile.attributes.multiRoot && devfile.attributes.multiRoot === 'on'; +}