Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/src/common/promise-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { CancellationToken, CancellationError, cancelled } from './cancellation'
*/
export class Deferred<T = void> {
state: 'resolved' | 'rejected' | 'unresolved' = 'unresolved';
resolve: (value: T) => void;
resolve: (value: T | PromiseLike<T>) => void;
reject: (err?: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any

promise = new Promise<T>((resolve, reject) => {
Expand Down
30 changes: 11 additions & 19 deletions packages/workspace/src/browser/workspace-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri';
import { WorkspaceServer, THEIA_EXT, CommonWorkspaceUtils } from '../common';
import { WorkspaceServer, CommonWorkspaceUtils } from '../common';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window';
import {
Expand Down Expand Up @@ -438,24 +438,16 @@ export class WorkspaceService implements FrontendApplicationContribution {
}

async getUntitledWorkspace(): Promise<URI> {
const configDirURI = await this.envVariableServer.getConfigDirUri();
let uri;
let attempts = 0;
do {
attempts++;
uri = new URI(configDirURI).resolve(`workspaces/Untitled-${Math.round(Math.random() * 1000)}.${THEIA_EXT}`);
if (attempts === 10) {
this.messageService.warn(nls.localize(
'theia/workspace/untitled-cleanup',
'There appear to be many untitled workspace files. Please check {0} and remove any unused files.',
new URI(configDirURI).resolve('workspaces').path.toString())
);
}
if (attempts === 50) {
throw new Error('Workspace Service: too many attempts to find unused filename.');
}
} while (await this.fileService.exists(uri));
return uri;
const configDirURI = new URI(await this.envVariableServer.getConfigDirUri());
return this.utils.getUntitledWorkspaceUri(
configDirURI,
uri => this.fileService.exists(uri).then(exists => !exists),
() => this.messageService.warn(nls.localize(
'theia/workspace/untitled-cleanup',
'There appear to be many untitled workspace files. Please check {0} and remove any unused files.',
configDirURI.resolve('workspaces').path.fsPath())
),
);
}

protected async writeWorkspaceFile(workspaceFile: FileStat | undefined, workspaceData: WorkspaceData): Promise<FileStat | undefined> {
Expand Down
18 changes: 18 additions & 0 deletions packages/workspace/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import URI from '@theia/core/lib/common/uri';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { injectable } from '@theia/core/shared/inversify';
import { FileStat } from '@theia/filesystem/lib/common/files';
import { MaybePromise } from '@theia/core';

export const THEIA_EXT = 'theia-workspace';
export const VSCODE_EXT = 'code-workspace';
Expand Down Expand Up @@ -47,4 +48,21 @@ export class CommonWorkspaceUtils {
isUntitledWorkspace(candidate?: URI): boolean {
return !!candidate && this.isWorkspaceFile(candidate) && candidate.path.base.startsWith('Untitled');
}

async getUntitledWorkspaceUri(configDirUri: URI, isAcceptable: (candidate: URI) => MaybePromise<boolean>, warnOnHits?: () => unknown): Promise<URI> {
const parentDir = configDirUri.resolve('workspaces');
let uri;
let attempts = 0;
do {
attempts++;
uri = parentDir.resolve(`Untitled-${Math.round(Math.random() * 1000)}.${THEIA_EXT}`);
if (attempts === 10) {
warnOnHits?.();
}
if (attempts === 50) {
throw new Error('Workspace Service: too many attempts to find unused filename.');
}
} while (!(await isAcceptable(uri)));
return uri;
}
}
53 changes: 38 additions & 15 deletions packages/workspace/src/node/default-workspace-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,55 @@ import URI from '@theia/core/lib/common/uri';
@injectable()
export class WorkspaceCliContribution implements CliContribution {

@inject(EnvVariablesServer) protected readonly envVariablesServer: EnvVariablesServer;
@inject(CommonWorkspaceUtils) protected readonly workspaceUtils: CommonWorkspaceUtils;

workspaceRoot = new Deferred<string | undefined>();

configure(conf: yargs.Argv): void {
conf.usage('$0 [workspace-directory] [options]');
conf.usage('$0 [workspace-directories] [options]');
conf.option('root-dir', {
description: 'DEPRECATED: Sets the workspace directory.',
});
}

setArguments(args: yargs.Arguments): void {
let wsPath: string | undefined = typeof args._[2] === 'undefined' ? undefined : String(args._[2]);
if (!wsPath) {
wsPath = args['root-dir'] as string;
if (!wsPath) {
this.workspaceRoot.resolve(undefined);
return;
}
async setArguments(args: yargs.Arguments): Promise<void> {
const workspaceArguments = args._.slice(2).map(probablyAlreadyString => String(probablyAlreadyString));
if (workspaceArguments.length === 0 && args['root-dir']) {
workspaceArguments.push(String(args['root-dir']));
}
if (!path.isAbsolute(wsPath)) {
const cwd = process.cwd();
wsPath = path.join(cwd, wsPath);
if (workspaceArguments.length === 0) {
this.workspaceRoot.resolve(undefined);
} else if (workspaceArguments.length === 1) {
this.workspaceRoot.resolve(this.normalizeWorkspaceArg(workspaceArguments[0]));
} else {
this.workspaceRoot.resolve(this.buildWorkspaceForMultipleArguments(workspaceArguments));
}
if (wsPath && wsPath.endsWith('/')) {
wsPath = wsPath.slice(0, -1);
}

protected normalizeWorkspaceArg(raw: string): string {
return path.resolve(raw).replace(/\/$/, '');
}

protected async buildWorkspaceForMultipleArguments(workspaceArguments: string[]): Promise<string | undefined> {
try {
const dirs = await Promise.all(workspaceArguments.map(async maybeDir => (await fs.stat(maybeDir).catch(() => undefined))?.isDirectory()));
const folders = workspaceArguments.filter((_, index) => dirs[index]).map(dir => ({ path: this.normalizeWorkspaceArg(dir) }));
if (folders.length < 2) {
return folders[0]?.path;
}
const untitledWorkspaceUri = await this.workspaceUtils.getUntitledWorkspaceUri(
new URI(await this.envVariablesServer.getConfigDirUri()),
async uri => !await fs.pathExists(uri.path.fsPath()),
);
const untitledWorkspacePath = untitledWorkspaceUri.path.fsPath();

await fs.ensureDir(path.dirname(untitledWorkspacePath));
await fs.writeFile(untitledWorkspacePath, JSON.stringify({ folders }, undefined, 4));
return untitledWorkspacePath;
} catch {
return undefined;
}
this.workspaceRoot.resolve(wsPath);
}
}

Expand Down