Skip to content

Commit

Permalink
Enable workspace file extension customization
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew committed Apr 14, 2023
1 parent 6c7f3b1 commit f0e6505
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 37 deletions.
20 changes: 9 additions & 11 deletions packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ILogger } from '@theia/core';
import { FileUri } from '@theia/core/lib/node';
import { PluginPaths } from './const';
import { PluginPathsService } from '../../common/plugin-paths-protocol';
import { THEIA_EXT, VSCODE_EXT, getTemporaryWorkspaceFileUri } from '@theia/workspace/lib/common';
import { CommonWorkspaceUtils } from '@theia/workspace/lib/common';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { PluginCliContribution } from '../plugin-cli-contribution';

Expand All @@ -43,6 +43,9 @@ export class PluginPathsServiceImpl implements PluginPathsService {
@inject(PluginCliContribution)
protected readonly cliContribution: PluginCliContribution;

@inject(CommonWorkspaceUtils)
protected readonly workspaceUtils: CommonWorkspaceUtils;

async getHostLogPath(): Promise<string> {
const parentLogsDir = await this.getLogsDirPath();

Expand Down Expand Up @@ -78,23 +81,18 @@ export class PluginPathsServiceImpl implements PluginPathsService {
}

protected async buildWorkspaceId(workspaceUri: string, rootUris: string[]): Promise<string> {
const untitledWorkspace = await getTemporaryWorkspaceFileUri(this.envServer);
const configDirUri = await this.envServer.getConfigDirUri();
const untitledWorkspace = await this.workspaceUtils.getUntitledWorkspaceUri(
new URI(configDirUri),
async uri => !await fs.pathExists(uri.path.fsPath())
);

if (untitledWorkspace.toString() === workspaceUri) {
// if workspace is temporary
// then let create a storage path for each set of workspace roots
const rootsStr = rootUris.sort().join(',');
return this.createHash(rootsStr);
} else {
let stat;
try {
stat = await fs.stat(FileUri.fsPath(workspaceUri));
} catch { /* no-op */ }
let displayName = new URI(workspaceUri).displayName;
if ((!stat || !stat.isDirectory()) && (displayName.endsWith(`.${THEIA_EXT}`) || displayName.endsWith(`.${VSCODE_EXT}`))) {
displayName = displayName.slice(0, displayName.lastIndexOf('.'));
}

return this.createHash(workspaceUri);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,5 @@ monaco.languages.register({
],
'filenames': [
'settings.json'
],
'extensions': [
'.theia-workspace'
]
});
1 change: 1 addition & 0 deletions packages/workspace/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"@theia/core": "1.36.0",
"@theia/filesystem": "1.36.0",
"@theia/monaco-editor-core": "1.72.3",
"@theia/variable-resolver": "1.36.0",
"jsonc-parser": "^2.2.0",
"valid-filename": "^2.0.1"
Expand Down
47 changes: 37 additions & 10 deletions packages/workspace/src/browser/workspace-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
import { FileDialogService, OpenFileDialogProps, FileDialogTreeFilters } from '@theia/filesystem/lib/browser';
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
import { WorkspaceService } from './workspace-service';
import { THEIA_EXT, VSCODE_EXT } from '../common';
import { CommonWorkspaceUtils, THEIA_EXT, VSCODE_EXT } from '../common';
import { WorkspaceCommands } from './workspace-commands';
import { QuickOpenWorkspace } from './quick-open-workspace';
import { WorkspacePreferences } from './workspace-preferences';
Expand All @@ -40,6 +40,8 @@ import { FileStat } from '@theia/filesystem/lib/common/files';
import { UntitledWorkspaceExitDialog } from './untitled-workspace-exit-dialog';
import { FilesystemSaveResourceService } from '@theia/filesystem/lib/browser/filesystem-save-resource-service';
import { StopReason } from '@theia/core/lib/common/frontend-application-state';
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
import * as monaco from '@theia/monaco-editor-core';

export enum WorkspaceStates {
/**
Expand Down Expand Up @@ -75,9 +77,19 @@ export class WorkspaceFrontendContribution implements CommandContribution, Keybi
@inject(EncodingRegistry) protected readonly encodingRegistry: EncodingRegistry;
@inject(PreferenceConfigurations) protected readonly preferenceConfigurations: PreferenceConfigurations;
@inject(FilesystemSaveResourceService) protected readonly saveService: FilesystemSaveResourceService;
@inject(CommonWorkspaceUtils) protected readonly workspaceUtils: CommonWorkspaceUtils;

configure(): void {
this.encodingRegistry.registerOverride({ encoding: UTF8, extension: THEIA_EXT });
monaco.languages.register({
id: 'jsonc',
'aliases': [
'JSON with Comments'
],
'extensions': [
`.${this.workspaceUtils.getWorkspaceExtension()}`
]
});
this.encodingRegistry.registerOverride({ encoding: UTF8, extension: this.workspaceUtils.getWorkspaceExtension() });
this.encodingRegistry.registerOverride({ encoding: UTF8, extension: VSCODE_EXT });
this.updateEncodingOverrides();

Expand Down Expand Up @@ -404,6 +416,7 @@ export class WorkspaceFrontendContribution implements CommandContribution, Keybi
return WorkspaceFrontendContribution.createOpenWorkspaceOpenFileDialogProps({
type,
electron,
filters: this.getWorkspaceDialogFileFilters()
});
}

Expand All @@ -421,12 +434,13 @@ export class WorkspaceFrontendContribution implements CommandContribution, Keybi
do {
selected = await this.fileDialogService.showSaveDialog({
title: WorkspaceCommands.SAVE_WORKSPACE_AS.label!,
filters: WorkspaceFrontendContribution.DEFAULT_FILE_FILTER
filters: this.getWorkspaceDialogFileFilters()
});
if (selected) {
const displayName = selected.displayName;
if (!displayName.endsWith(`.${THEIA_EXT}`) && !displayName.endsWith(`.${VSCODE_EXT}`)) {
selected = selected.parent.resolve(`${displayName}.${THEIA_EXT}`);
if (!displayName.endsWith(`.${this.workspaceUtils.getWorkspaceExtension()}`)
&& !(this.workspaceUtils.isVSCodeWorkspaceSelectionEnabled() && displayName.endsWith(`.${VSCODE_EXT}`))) {
selected = selected.parent.resolve(`${displayName}.${this.workspaceUtils.getWorkspaceExtension()}`);
}
exist = await this.fileService.exists(selected);
if (exist) {
Expand Down Expand Up @@ -469,6 +483,17 @@ export class WorkspaceFrontendContribution implements CommandContribution, Keybi
return 'empty';
}

protected getWorkspaceDialogFileFilters(): FileDialogTreeFilters {
const workspaceExt = this.workspaceUtils.getWorkspaceExtension();
const filters: FileDialogTreeFilters = {
[`${nls.localizeByDefault('{0} workspace', FrontendApplicationConfigProvider.get().applicationName)} (*.${workspaceExt})`]: [workspaceExt]
};
if (this.workspaceUtils.isVSCodeWorkspaceSelectionEnabled()) {
filters[`${nls.localizeByDefault('{0} workspace', 'VS Code')} (*.${VSCODE_EXT})`] = [VSCODE_EXT];
}
return filters;
}

private isElectron(): boolean {
return environment.electron.is();
}
Expand Down Expand Up @@ -514,6 +539,8 @@ export namespace WorkspaceFrontendContribution {

/**
* File filter for all Theia and VS Code workspace file types.
*
* @deprecated Since 1.37.0 Use `WorkspaceFrontendContribution#getWorkspaceDialogFileFilters` instead.
*/
export const DEFAULT_FILE_FILTER: FileDialogTreeFilters = {
'Theia Workspace (*.theia-workspace)': [THEIA_EXT],
Expand All @@ -523,16 +550,16 @@ export namespace WorkspaceFrontendContribution {
/**
* Returns with an `OpenFileDialogProps` for opening the `Open Workspace` dialog.
*/
export function createOpenWorkspaceOpenFileDialogProps(options: Readonly<{ type: OS.Type, electron: boolean }>): OpenFileDialogProps {
const { electron, type } = options;
export function createOpenWorkspaceOpenFileDialogProps(options: Readonly<{ type: OS.Type, electron: boolean, filters?: FileDialogTreeFilters }>): OpenFileDialogProps {
const { electron, type, filters = DEFAULT_FILE_FILTER } = options;
const title = WorkspaceCommands.OPEN_WORKSPACE.dialogLabel;
// If browser
if (!electron) {
return {
title,
canSelectFiles: true,
canSelectFolders: true,
filters: DEFAULT_FILE_FILTER
filters
};
}

Expand All @@ -543,15 +570,15 @@ export namespace WorkspaceFrontendContribution {
title,
canSelectFiles: true,
canSelectFolders: true,
filters: DEFAULT_FILE_FILTER
filters
};
}

return {
title,
canSelectFiles: true,
canSelectFolders: false,
filters: DEFAULT_FILE_FILTER
filters
};
}

Expand Down
8 changes: 7 additions & 1 deletion packages/workspace/src/browser/workspace-schema-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { InMemoryResources, isArray, isObject } from '@theia/core/lib/common';
import { IJSONSchema } from '@theia/core/lib/common/json-schema';
import URI from '@theia/core/lib/common/uri';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { CommonWorkspaceUtils, VSCODE_EXT } from '../common';

export interface SchemaUpdateMessage {
key: string,
Expand All @@ -39,6 +40,7 @@ export class WorkspaceSchemaUpdater implements JsonSchemaContribution {
protected safeToHandleQueue = new Deferred();

@inject(InMemoryResources) protected readonly inmemoryResources: InMemoryResources;
@inject(CommonWorkspaceUtils) protected readonly workspaceUtils: CommonWorkspaceUtils;

@postConstruct()
protected init(): void {
Expand All @@ -47,8 +49,12 @@ export class WorkspaceSchemaUpdater implements JsonSchemaContribution {
}

registerSchemas(context: JsonSchemaRegisterContext): void {
const extensions = [this.workspaceUtils.getWorkspaceExtension()];
if (this.workspaceUtils.isVSCodeWorkspaceSelectionEnabled()) {
extensions.push(VSCODE_EXT);
}
context.registerSchema({
fileMatch: ['*.theia-workspace', '*.code-workspace'],
fileMatch: extensions.map(ext => `*.${ext}`),
url: this.uri.toString()
});
}
Expand Down
35 changes: 23 additions & 12 deletions packages/workspace/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,15 @@
// TODO get rid of util files, replace with methods in a responsible class

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';

/**
* @deprecated since 1.4.0 - because of https://github.com/eclipse-theia/theia/tree/master/doc/coding-guidelines.md#di-function-export,
* use `WorkspaceService.getUntitledWorkspace` instead
* @deprecated Since 1.37.0. Use `CommonWorkspaceUtils#getWorkspaceExtension` instead.
*/
export async function getTemporaryWorkspaceFileUri(envVariableServer: EnvVariablesServer): Promise<URI> {
const configDirUri = await envVariableServer.getConfigDirUri();
return new URI(configDirUri).resolve(`Untitled.${THEIA_EXT}`);
}
export const THEIA_EXT = 'theia-workspace';
export const VSCODE_EXT = 'code-workspace';

@injectable()
export class CommonWorkspaceUtils {
Expand All @@ -43,20 +36,38 @@ export class CommonWorkspaceUtils {
*/
isWorkspaceFile(candidate: FileStat | URI): boolean {
const uri = FileStat.is(candidate) ? candidate.resource : candidate;
return uri.path.ext === `.${THEIA_EXT}` || uri.path.ext === `.${VSCODE_EXT}`;
return uri.path.ext === `.${this.getWorkspaceExtension()}` || (this.isVSCodeWorkspaceSelectionEnabled() && uri.path.ext === `.${VSCODE_EXT}`);
}

isUntitledWorkspace(candidate?: URI): boolean {
return !!candidate && this.isWorkspaceFile(candidate) && candidate.path.base.startsWith('Untitled');
}

/**
* Determines whether the workspace service allows to select `.code-workspace` as valid workspaces for the application.
*
* If this method returns `false`, only workspace file that fit the `CommonWorkspaceUtils#getWorkspaceExtension` return value can be selected.
*/
isVSCodeWorkspaceSelectionEnabled(): boolean {
return true;
}

/**
* Returns the file extension used for all workspace files for this application.
*
* Returns `theia-workspace` by default.
*/
getWorkspaceExtension(): string {
return THEIA_EXT;
}

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}`);
uri = parentDir.resolve(`Untitled-${Math.round(Math.random() * 1000)}.${this.getWorkspaceExtension()}`);
if (attempts === 10) {
warnOnHits?.();
}
Expand Down

0 comments on commit f0e6505

Please sign in to comment.