Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Open Che workspace with multiple workspace root #778

Merged
merged 10 commits into from
Feb 25, 2021
Merged
Original file line number Diff line number Diff line change
@@ -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<void> {
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();
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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();
Expand Down Expand Up @@ -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();
});
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -127,10 +137,22 @@ export class PluginRemoteInit {
const originalStart = PluginManagerExtImpl.prototype.$start;
PluginManagerExtImpl.prototype.$start = async function (params: PluginManagerStartParams): Promise<void> {
const { hostLogPath, hostStoragePath, hostGlobalStoragePath } = params.configStorage;

const overriddenLogPath = modifyPathToLocal(hostLogPath);
await fs.ensureDir(overriddenLogPath);
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved

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);
Expand Down
4 changes: 3 additions & 1 deletion extensions/eclipse-che-theia-workspace/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"src"
],
"dependencies": {
"@theia/core": "next",
"@eclipse-che/api": "latest",
"@theia/workspace": "next",
"@eclipse-che/theia-remote-api": "^0.0.1",
Expand All @@ -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"
}
]
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="theia-navigator-container">
<div className="center">No projects in the workspace yet</div>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**********************************************************************
* 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 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';

interface TheiaWorkspace {
folders: TheiaWorkspacePath[];
}

interface TheiaWorkspacePath {
path: string;
}

@injectable()
export class CheWorkspaceServer extends DefaultWorkspaceServer {
@inject(WorkspaceService)
protected workspaceService: WorkspaceService;

// 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<string | undefined> {
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) {
// no, then create the file
const theiaWorkspace: TheiaWorkspace = { folders: [] };
await fs.writeFile(cheTheiaWorkspaceFile, JSON.stringify(theiaWorkspace), { encoding: 'utf8' });
}

return cheTheiaWorkspaceFileUri.toString();
}
}

function isMultiRoot(workspace: Workspace): boolean {
const devfile = workspace.devfile;
return !!devfile && !!devfile.attributes && !!devfile.attributes.multiRoot && devfile.attributes.multiRoot === 'on';
}
11 changes: 11 additions & 0 deletions extensions/eclipse-che-theia-workspace/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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 { 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);
});
1 change: 1 addition & 0 deletions plugins/task-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
3 changes: 2 additions & 1 deletion plugins/task-plugin/src/che-task-backend-module.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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>(ConfigurationsExporter).to(TaskConfigurationsExporter).inSingletonScope();
container.bind<ConfigurationsExporter>(ConfigurationsExporter).to(LaunchConfigurationsExporter).inSingletonScope();
container.bind(ExportConfigurationsManager).toSelf().inSingletonScope();
Expand Down
17 changes: 14 additions & 3 deletions plugins/task-plugin/src/export/export-configs-manager.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,6 +11,7 @@
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');
Expand Down Expand Up @@ -42,11 +43,21 @@ export class ExportConfigurationsManager {
@multiInject(ConfigurationsExporter)
protected readonly exporters: ConfigurationsExporter[];

@inject(LaunchConfigurationsExporter)
protected readonly launchConfigurationsExporter: LaunchConfigurationsExporter;
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved

protected cheCommands: cheApi.workspace.Command[] = [];

async init(): Promise<void> {
this.cheCommands = await this.cheWorkspaceClient.getCommands();
this.launchConfigurationsExporter.init(this.cheCommands);
this.export();
}

async export(): Promise<void> {
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
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));
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
}

await Promise.all(exportPromises);
Expand Down
Loading