未验证 提交 c7cb19ed 编写于 作者: B Benjamin Pasero 提交者: GitHub

Merge pull request #114749 from microsoft/ben/folder-id

Introduce and adopt ISingleFolderWorkspaceIdentifier
......@@ -81,8 +81,7 @@
* colorScheme: ('light' | 'dark' | 'hc'),
* autoDetectHighContrast?: boolean,
* extensionDevelopmentPath?: string[],
* folderUri?: object,
* workspace?: object
* workspace?: import('../../../platform/workspaces/common/workspaces').IWorkspaceIdentifier | import('../../../platform/workspaces/common/workspaces').ISingleFolderWorkspaceIdentifier
* }} configuration
*/
function showPartsSplash(configuration) {
......@@ -161,7 +160,7 @@
splash.appendChild(activityDiv);
// part: side bar (only when opening workspace/folder)
if (configuration.folderUri || configuration.workspace) {
if (configuration.workspace) {
// folder or workspace -> status bar color, sidebar
const sideDiv = document.createElement('div');
sideDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: ${layoutInfo.activityBarWidth}px; width: ${layoutInfo.sideBarWidth}px; background-color: ${colorInfo.sideBarBackground};`);
......@@ -170,7 +169,7 @@
// part: statusbar
const statusDiv = document.createElement('div');
statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.folderUri || configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`);
statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`);
splash.appendChild(statusDiv);
document.body.appendChild(splash);
......
......@@ -20,7 +20,7 @@ import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
......@@ -360,13 +360,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
get lastFocusTime(): number { return this._lastFocusTime; }
get backupPath(): string | undefined { return this.currentConfig ? this.currentConfig.backupPath : undefined; }
get backupPath(): string | undefined { return this.currentConfig?.backupPath; }
get openedWorkspace(): IWorkspaceIdentifier | undefined { return this.currentConfig ? this.currentConfig.workspace : undefined; }
get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this.currentConfig?.workspace; }
get openedFolderUri(): URI | undefined { return this.currentConfig ? this.currentConfig.folderUri : undefined; }
get remoteAuthority(): string | undefined { return this.currentConfig ? this.currentConfig.remoteAuthority : undefined; }
get remoteAuthority(): string | undefined { return this.currentConfig?.remoteAuthority; }
setReady(): void {
this._readyState = ReadyState.READY;
......
......@@ -12,10 +12,9 @@ import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snip
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, splitLines } from 'vs/base/common/strings';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService } from 'vs/platform/label/common/label';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { URI } from 'vs/base/common/uri';
import { OvertypingCapturer } from 'vs/editor/contrib/suggest/suggestOvertypingCapturer';
import { generateUuid } from 'vs/base/common/uuid';
......@@ -314,9 +313,9 @@ export class WorkspaceBasedVariableResolver implements VariableResolver {
return undefined;
}
private _resolveWorkspaceName(workspaceIdentifier: IWorkspaceIdentifier | URI): string | undefined {
private _resolveWorkspaceName(workspaceIdentifier: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string | undefined {
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
return path.basename(workspaceIdentifier.path);
return path.basename(workspaceIdentifier.uri.path);
}
let filename = path.basename(workspaceIdentifier.configPath.path);
......@@ -325,9 +324,9 @@ export class WorkspaceBasedVariableResolver implements VariableResolver {
}
return filename;
}
private _resoveWorkspacePath(workspaceIdentifier: IWorkspaceIdentifier | URI): string | undefined {
private _resoveWorkspacePath(workspaceIdentifier: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string | undefined {
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
return normalizeDriveLetter(workspaceIdentifier.fsPath);
return normalizeDriveLetter(workspaceIdentifier.uri.fsPath);
}
let filename = path.basename(workspaceIdentifier.configPath.path);
......
......@@ -638,7 +638,7 @@ export class SimpleWorkspaceContextService implements IWorkspaceContextService {
return resource && resource.scheme === SimpleWorkspaceContextService.SCHEME;
}
public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
public isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean {
return true;
}
}
......@@ -736,7 +736,7 @@ export class SimpleUriLabelService implements ILabelService {
return basename(resource);
}
public getWorkspaceLabel(workspace: IWorkspaceIdentifier | URI | IWorkspace, options?: { verbose: boolean; }): string {
public getWorkspaceLabel(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | IWorkspace, options?: { verbose: boolean; }): string {
return '';
}
......
......@@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { IWorkspace } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export const ILabelService = createDecorator<ILabelService>('labelService');
......@@ -23,7 +23,7 @@ export interface ILabelService {
*/
getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean }): string;
getUriBasenameLabel(resource: URI): string;
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string;
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | IWorkspace), options?: { verbose: boolean }): string;
getHostLabel(scheme: string, authority?: string): string;
getSeparator(scheme: string, authority?: string): '/' | '\\';
......
......@@ -20,6 +20,7 @@ import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemote
import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/node/launch';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export const ID = 'launchMainService';
export const ILaunchMainService = createDecorator<ILaunchMainService>(ID);
......@@ -272,12 +273,11 @@ export class LaunchMainService implements ILaunchMainService {
private getFolderURIs(window: ICodeWindow): URI[] {
const folderURIs: URI[] = [];
if (window.openedFolderUri) {
folderURIs.push(window.openedFolderUri);
} else if (window.openedWorkspace) {
// workspace folders can only be shown for local workspaces
const workspaceConfigPath = window.openedWorkspace.configPath;
const resolvedWorkspace = this.workspacesMainService.resolveLocalWorkspaceSync(workspaceConfigPath);
const workspace = window.openedWorkspace;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
folderURIs.push(workspace.uri);
} else if (isWorkspaceIdentifier(workspace)) {
const resolvedWorkspace = this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath); // workspace folders can only be shown for local (resolved) workspaces
if (resolvedWorkspace) {
const rootFolders = resolvedWorkspace.folders;
rootFolders.forEach(root => {
......
......@@ -105,7 +105,6 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
return windows.map(window => ({
id: window.id,
workspace: window.openedWorkspace,
folderUri: window.openedFolderUri,
title: window.win.getTitle(),
filename: window.getRepresentedFilename(),
dirty: window.isDocumentEdited()
......
......@@ -12,7 +12,7 @@ import { mark } from 'vs/base/common/performance';
import { join } from 'vs/base/common/path';
import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { assertIsDefined } from 'vs/base/common/types';
import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
......@@ -148,23 +148,22 @@ export class NativeStorageService extends AbstractStorageService {
private ensureWorkspaceStorageFolderMeta(payload: IWorkspaceInitializationPayload): void {
let meta: object | undefined = undefined;
if (isSingleFolderWorkspaceInitializationPayload(payload)) {
meta = { folder: payload.folder.toString() };
if (isSingleFolderWorkspaceIdentifier(payload)) {
meta = { folder: payload.uri.toString() };
} else if (isWorkspaceIdentifier(payload)) {
meta = { configuration: payload.configPath };
meta = { workspace: payload.configPath.toString() };
}
if (meta) {
const logService = this.logService;
const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME);
(async function () {
(async () => {
try {
const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME);
const storageExists = await exists(workspaceStorageMetaPath);
if (!storageExists) {
await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2));
}
} catch (error) {
logService.error(error);
this.logService.error(error);
}
})();
}
......
......@@ -41,8 +41,7 @@ export interface IAddFoldersRequest {
export interface IOpenedWindow {
readonly id: number;
readonly workspace?: IWorkspaceIdentifier;
readonly folderUri?: ISingleFolderWorkspaceIdentifier;
readonly workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
readonly title: string;
readonly filename?: string;
readonly dirty: boolean;
......@@ -240,8 +239,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
nodeCachedDataDir?: string;
partsSplashPath: string;
workspace?: IWorkspaceIdentifier;
folderUri?: ISingleFolderWorkspaceIdentifier;
workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
isInitialStartup?: boolean;
logLevel: LogLevel;
......
......@@ -8,7 +8,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { Rectangle, BrowserWindow, WebContents } from 'electron';
......@@ -65,8 +65,8 @@ export interface ICodeWindow extends IDisposable {
readonly win: BrowserWindow;
readonly config: INativeWindowConfiguration | undefined;
readonly openedFolderUri?: URI;
readonly openedWorkspace?: IWorkspaceIdentifier;
readonly openedWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
readonly backupPath?: string;
readonly remoteAuthority?: string;
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IWorkspaceIdentifier, IResolvedWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, IResolvedWorkspace, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
......@@ -13,7 +13,7 @@ export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWork
// First check for windows with workspaces that have a parent folder of the provided path opened
for (const window of windows) {
const workspace = window.openedWorkspace;
if (workspace) {
if (isWorkspaceIdentifier(workspace)) {
const resolvedWorkspace = localWorkspaceResolver(workspace);
// resolved workspace: folders are known and can be compared with
......@@ -33,9 +33,9 @@ export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWork
}
// Then go with single folder windows that are parent of the provided file path
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedFolderUri));
const singleFolderWindowsOnFilePath = windows.filter(window => isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedWorkspace.uri));
if (singleFolderWindowsOnFilePath.length) {
return singleFolderWindowsOnFilePath.sort((windowA, windowB) => -(windowA.openedFolderUri!.path.length - windowB.openedFolderUri!.path.length))[0];
return singleFolderWindowsOnFilePath.sort((windowA, windowB) => -((windowA.openedWorkspace as ISingleFolderWorkspaceIdentifier).uri.path.length - (windowB.openedWorkspace as ISingleFolderWorkspaceIdentifier).uri.path.length))[0];
}
return undefined;
......@@ -46,12 +46,12 @@ export function findWindowOnWorkspaceOrFolder(windows: ICodeWindow[], folderOrWo
for (const window of windows) {
// check for workspace config path
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, folderOrWorkspaceConfigUri)) {
if (isWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, folderOrWorkspaceConfigUri)) {
return window;
}
// check for folder path
if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, folderOrWorkspaceConfigUri)) {
if (isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.uri, folderOrWorkspaceConfigUri)) {
return window;
}
}
......
......@@ -15,7 +15,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/windows/common/windows';
import { ICodeWindow, IWindowsMainService, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export interface IWindowState {
workspace?: IWorkspaceIdentifier;
......@@ -198,10 +198,10 @@ export class WindowsStateHandler extends Disposable {
}
// Any non extension host window with same workspace or folder
else if (!window.isExtensionDevelopmentHost && (!!window.openedWorkspace || !!window.openedFolderUri)) {
else if (!window.isExtensionDevelopmentHost && window.openedWorkspace) {
this._state.openedWindows.forEach(openedWindow => {
const sameWorkspace = window.openedWorkspace && openedWindow.workspace && openedWindow.workspace.id === window.openedWorkspace.id;
const sameFolder = window.openedFolderUri && openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, window.openedFolderUri);
const sameWorkspace = isWorkspaceIdentifier(window.openedWorkspace) && openedWindow.workspace?.id === window.openedWorkspace.id;
const sameFolder = isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, window.openedWorkspace.uri);
if (sameWorkspace || sameFolder) {
openedWindow.uiState = state.uiState;
......@@ -220,8 +220,8 @@ export class WindowsStateHandler extends Disposable {
private toWindowState(window: ICodeWindow): IWindowState {
return {
workspace: window.openedWorkspace,
folderUri: window.openedFolderUri,
workspace: isWorkspaceIdentifier(window.openedWorkspace) ? window.openedWorkspace : undefined,
folderUri: isSingleFolderWorkspaceIdentifier(window.openedWorkspace) ? window.openedWorkspace.uri : undefined,
backupPath: window.backupPath,
remoteAuthority: window.remoteAuthority,
uiState: window.serializeWindowState()
......@@ -272,16 +272,16 @@ export class WindowsStateHandler extends Disposable {
// Known Workspace - load from stored settings
const workspace = configuration.workspace;
if (workspace) {
const stateForWorkspace = this.state.openedWindows.filter(openedWindow => openedWindow.workspace && openedWindow.workspace.id === workspace.id).map(o => o.uiState);
if (isWorkspaceIdentifier(workspace)) {
const stateForWorkspace = this.state.openedWindows.filter(openedWindow => openedWindow.workspace && openedWindow.workspace.id === workspace.id).map(openedWindow => openedWindow.uiState);
if (stateForWorkspace.length) {
return stateForWorkspace[0];
}
}
// Known Folder - load from stored settings
if (configuration.folderUri) {
const stateForFolder = this.state.openedWindows.filter(openedWindow => openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, configuration.folderUri)).map(o => o.uiState);
if (isSingleFolderWorkspaceIdentifier(workspace)) {
const stateForFolder = this.state.openedWindows.filter(openedWindow => openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, workspace.uri)).map(openedWindow => openedWindow.uiState);
if (stateForFolder.length) {
return stateForFolder[0];
}
......@@ -289,7 +289,7 @@ export class WindowsStateHandler extends Disposable {
// Empty windows with backups
else if (configuration.backupPath) {
const stateForEmptyWindow = this.state.openedWindows.filter(openedWindow => openedWindow.backupPath === configuration.backupPath).map(o => o.uiState);
const stateForEmptyWindow = this.state.openedWindows.filter(openedWindow => openedWindow.backupPath === configuration.backupPath).map(openedWindow => openedWindow.uiState);
if (stateForEmptyWindow.length) {
return stateForEmptyWindow[0];
}
......
......@@ -39,8 +39,7 @@ function createTestCodeWindow(options: { lastFocusTime: number, openedFolderUri?
id: number = -1;
win: Electron.BrowserWindow = undefined!;
config: INativeWindowConfiguration | undefined;
openedFolderUri = options.openedFolderUri;
openedWorkspace = options.openedWorkspace;
openedWorkspace = options.openedFolderUri ? { id: '', uri: options.openedFolderUri } : options.openedWorkspace;
backupPath?: string | undefined;
remoteAuthority?: string | undefined;
isExtensionDevelopmentHost = false;
......
......@@ -58,9 +58,9 @@ export interface IWorkspaceContextService extends IWorkspaceFolderProvider {
getWorkspaceFolder(resource: URI): IWorkspaceFolder | null;
/**
* Return `true` if the current workspace has the given identifier otherwise `false`.
* Return `true` if the current workspace has the given identifier or root URI otherwise `false`.
*/
isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean;
isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean;
/**
* Returns if the provided resource is inside the workspace or not.
......
......@@ -22,9 +22,16 @@ import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export const WORKSPACE_EXTENSION = 'code-workspace';
const WORKSPACE_SUFFIX = `.${WORKSPACE_EXTENSION}`;
export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }];
export const UNTITLED_WORKSPACE_NAME = 'workspace.json';
export function hasWorkspaceFileExtension(path: string | URI) {
const ext = (typeof path === 'string') ? extname(path) : resourceExtname(path);
return ext === WORKSPACE_SUFFIX;
}
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
export interface IWorkspacesService {
......@@ -48,6 +55,8 @@ export interface IWorkspacesService {
getDirtyWorkspaces(): Promise<Array<IWorkspaceIdentifier | URI>>;
}
//#region Workspaces Recently Opened
export interface IRecentlyOpened {
workspaces: Array<IRecentWorkspace | IRecentFolder>;
files: IRecentFile[];
......@@ -61,7 +70,7 @@ export interface IRecentWorkspace {
}
export interface IRecentFolder {
folderUri: ISingleFolderWorkspaceIdentifier;
folderUri: URI;
label?: string;
}
......@@ -82,36 +91,114 @@ export function isRecentFile(curr: IRecent): curr is IRecentFile {
return curr.hasOwnProperty('fileUri');
}
//#endregion
//#region Identifiers / Payload
export interface IBaseWorkspaceIdentifier {
/**
* Every workspace (multi-root, single folder or empty)
* has a unique identifier. It is not possible to open
* a workspace with the same `id` in multiple windows
*/
id: string;
}
/**
* A single folder workspace identifier is just the path to the folder.
* A single folder workspace identifier is a path to a folder + id.
*/
export type ISingleFolderWorkspaceIdentifier = URI;
export interface ISingleFolderWorkspaceIdentifier extends IBaseWorkspaceIdentifier {
export interface IWorkspaceIdentifier {
id: string;
/**
* Folder path as `URI`.
*/
uri: URI;
}
export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleFolderWorkspaceIdentifier {
const singleFolderIdentifier = obj as ISingleFolderWorkspaceIdentifier | undefined;
return typeof singleFolderIdentifier?.id === 'string' && URI.isUri(singleFolderIdentifier.uri);
}
/**
* A multi-root workspace identifier is a path to a workspace file + id.
*/
export interface IWorkspaceIdentifier extends IBaseWorkspaceIdentifier {
/**
* Workspace config file path as `URI`.
*/
configPath: URI;
}
export function reviveWorkspaceIdentifier(workspace: { id: string, configPath: UriComponents; }): IWorkspaceIdentifier {
return { id: workspace.id, configPath: URI.revive(workspace.configPath) };
export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined {
// Multi root
if (workspace.configuration) {
return {
id: workspace.id,
configPath: workspace.configuration
};
}
// Single folder
if (workspace.folders.length === 1) {
return {
id: workspace.id,
uri: workspace.folders[0].uri
};
}
// Empty workspace
return undefined;
}
export function isStoredWorkspaceFolder(thing: unknown): thing is IStoredWorkspaceFolder {
return isRawFileWorkspaceFolder(thing) || isRawUriWorkspaceFolder(thing);
export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier {
const workspaceIdentifier = obj as IWorkspaceIdentifier | undefined;
return typeof workspaceIdentifier?.id === 'string' && URI.isUri(workspaceIdentifier.configPath);
}
export function isRawFileWorkspaceFolder(thing: any): thing is IRawFileWorkspaceFolder {
return thing
&& typeof thing === 'object'
&& typeof thing.path === 'string'
&& (!thing.name || typeof thing.name === 'string');
export function reviveIdentifier(identifier: { id: string, uri?: UriComponents, configPath?: UriComponents } | undefined): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined {
if (identifier?.uri) {
return { id: identifier.id, uri: URI.revive(identifier.uri) };
}
if (identifier?.configPath) {
return { id: identifier.id, configPath: URI.revive(identifier.configPath) };
}
return undefined;
}
export function isRawUriWorkspaceFolder(thing: any): thing is IRawUriWorkspaceFolder {
return thing
&& typeof thing === 'object'
&& typeof thing.uri === 'string'
&& (!thing.name || typeof thing.name === 'string');
export function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean {
return extUriBiasedIgnorePathCase.isEqualOrParent(path, environmentService.untitledWorkspacesHome);
}
export interface IEmptyWorkspaceInitializationPayload extends IBaseWorkspaceIdentifier { }
export type IWorkspaceInitializationPayload = IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceInitializationPayload;
//#endregion
//#region Workspace File Utilities
export function isStoredWorkspaceFolder(obj: unknown): obj is IStoredWorkspaceFolder {
return isRawFileWorkspaceFolder(obj) || isRawUriWorkspaceFolder(obj);
}
export function isRawFileWorkspaceFolder(obj: unknown): obj is IRawFileWorkspaceFolder {
const candidate = obj as IRawFileWorkspaceFolder | undefined;
return typeof candidate?.path === 'string' && (!candidate.name || typeof candidate.name === 'string');
}
export function isRawUriWorkspaceFolder(obj: unknown): obj is IRawUriWorkspaceFolder {
const candidate = obj as IRawUriWorkspaceFolder | undefined;
return typeof candidate?.uri === 'string' && (!candidate.name || typeof candidate.name === 'string');
}
export interface IRawFileWorkspaceFolder {
......@@ -151,56 +238,6 @@ export interface IEnterWorkspaceResult {
backupPath?: string;
}
export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleFolderWorkspaceIdentifier {
return obj instanceof URI;
}
export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier {
const workspaceIdentifier = obj as IWorkspaceIdentifier;
return workspaceIdentifier && typeof workspaceIdentifier.id === 'string' && workspaceIdentifier.configPath instanceof URI;
}
export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined {
if (workspace.configuration) {
return {
configPath: workspace.configuration,
id: workspace.id
};
}
if (workspace.folders.length === 1) {
return workspace.folders[0].uri;
}
// Empty workspace
return undefined;
}
export function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean {
return extUriBiasedIgnorePathCase.isEqualOrParent(path, environmentService.untitledWorkspacesHome);
}
export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier;
export interface ISingleFolderWorkspaceInitializationPayload { id: string; folder: ISingleFolderWorkspaceIdentifier; }
export interface IEmptyWorkspaceInitializationPayload { id: string; }
export type IWorkspaceInitializationPayload = IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload;
export function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is ISingleFolderWorkspaceInitializationPayload {
return isSingleFolderWorkspaceIdentifier((obj.folder as ISingleFolderWorkspaceIdentifier));
}
const WORKSPACE_SUFFIX = '.' + WORKSPACE_EXTENSION;
export function hasWorkspaceFileExtension(path: string | URI) {
const ext = (typeof path === 'string') ? extname(path) : resourceExtname(path);
return ext === WORKSPACE_SUFFIX;
}
const SLASH = '/';
/**
* Given a folder URI and the workspace config folder, computes the IStoredWorkspaceFolder using
* a relative or absolute path or a uri.
......@@ -308,12 +345,14 @@ function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace {
export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean {
if (isWindows) {
return storedFolders.some(folder => isRawFileWorkspaceFolder(folder) && folder.path.indexOf(SLASH) >= 0);
return storedFolders.some(folder => isRawFileWorkspaceFolder(folder) && folder.path.indexOf('/') >= 0);
}
return true;
}
//#endregion
//#region Workspace Storage
interface ISerializedRecentlyOpened {
......
......@@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { getBaseLabel, getPathLabel, splitName } from 'vs/base/common/labels';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile, toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile, toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData, WORKSPACE_EXTENSION, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { ThrottledDelayer } from 'vs/base/common/async';
import { dirname, originalFSPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
......@@ -247,13 +247,10 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
// Add current workspace to beginning if set
const currentWorkspace = include?.config?.workspace;
if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) {
if (isWorkspaceIdentifier(currentWorkspace) && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) {
workspaces.push({ workspace: currentWorkspace });
}
const currentFolder = include?.config?.folderUri;
if (currentFolder) {
workspaces.push({ folderUri: currentFolder });
} else if (isSingleFolderWorkspaceIdentifier(currentWorkspace)) {
workspaces.push({ folderUri: currentWorkspace.uri });
}
// Add currently files to open to the beginning if any
......@@ -357,7 +354,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
let description;
let args;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
if (URI.isUri(workspace)) {
description = localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService));
args = `--folder-uri "${workspace.toString()}"`;
} else {
......@@ -399,7 +396,9 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
}
private getSimpleWorkspaceLabel(workspace: IWorkspaceIdentifier | URI, workspaceHome: URI): string {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
// Single Folder
if (URI.isUri(workspace)) {
return basename(workspace);
}
......@@ -408,6 +407,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: normal
let filename = basename(workspace.configPath);
if (filename.endsWith(WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
......@@ -433,7 +433,7 @@ function indexOfWorkspace(arr: IRecent[], candidate: IWorkspaceIdentifier): numb
return arr.findIndex(workspace => isRecentWorkspace(workspace) && workspace.workspace.id === candidate.id);
}
function indexOfFolder(arr: IRecent[], candidate: ISingleFolderWorkspaceIdentifier): number {
function indexOfFolder(arr: IRecent[], candidate: URI): number {
return arr.findIndex(folder => isRecentFolder(folder) && extUriBiasedIgnorePathCase.isEqual(folder.folderUri, candidate));
}
......
......@@ -3,12 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace, isWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { join, dirname } from 'vs/base/common/path';
import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs';
import { readFileSync, existsSync, mkdirSync } from 'fs';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { readFileSync, existsSync, mkdirSync, statSync, Stats } from 'fs';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { Event, Emitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { createHash } from 'crypto';
......@@ -271,7 +271,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
return true;
}
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, workspacePath)) {
if (isWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, workspacePath)) {
return false; // window is already opened on a workspace with that path
}
......@@ -308,12 +308,11 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
}
// if the window was opened on an untitled workspace, delete it.
if (window.openedWorkspace && this.isUntitledWorkspace(window.openedWorkspace)) {
if (isWorkspaceIdentifier(window.openedWorkspace) && this.isUntitledWorkspace(window.openedWorkspace)) {
this.deleteUntitledWorkspaceSync(window.openedWorkspace);
}
// Update window configuration properly based on transition to workspace
window.config.folderUri = undefined;
window.config.workspace = workspace;
window.config.backupPath = backupPath;
......@@ -321,18 +320,76 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
}
}
function getWorkspaceId(configPath: URI): string {
let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString();
if (!isLinux) {
workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system
}
return createHash('md5').update(workspaceConfigPath).digest('hex');
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier {
function getWorkspaceId(): string {
let configPathStr = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString();
if (!isLinux) {
configPathStr = configPathStr.toLowerCase(); // sanitize for platform file system
}
return createHash('md5').update(configPathStr).digest('hex');
}
return {
configPath,
id: getWorkspaceId(configPath)
id: getWorkspaceId(),
configPath
};
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
export function getSingleFolderWorkspaceIdentifier(folderUri: URI): ISingleFolderWorkspaceIdentifier | undefined;
export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat: Stats): ISingleFolderWorkspaceIdentifier;
export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat?: Stats): ISingleFolderWorkspaceIdentifier | undefined {
function getFolderId(): string | undefined {
// Remote: produce a hash from the entire URI
if (folderUri.scheme !== Schemas.file) {
return createHash('md5').update(folderUri.toString()).digest('hex');
}
// Local: produce a hash from the path and include creation time as salt
if (!folderStat) {
try {
folderStat = statSync(folderUri.fsPath);
} catch (error) {
return undefined; // folder does not exist
}
}
let ctime: number | undefined;
if (isLinux) {
ctime = folderStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
} else if (isMacintosh) {
ctime = folderStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
} else if (isWindows) {
if (typeof folderStat.birthtimeMs === 'number') {
ctime = Math.floor(folderStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
} else {
ctime = folderStat.birthtime.getTime();
}
}
// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
// deleted and recreated. in that case we do not want to carry over previous state
return createHash('md5').update(folderUri.fsPath).update(ctime ? String(ctime) : '').digest('hex');
}
const folderId = getFolderId();
if (typeof folderId === 'string') {
return {
id: folderId,
uri: folderUri
};
}
return undefined; // invalid folder
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { hasWorkspaceFileExtension, toWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
suite('Workspaces', () => {
test('hasWorkspaceFileExtension', () => {
assert.strictEqual(hasWorkspaceFileExtension('something'), false);
assert.strictEqual(hasWorkspaceFileExtension('something.code-workspace'), true);
});
test('toWorkspaceIdentifier', () => {
let identifier = toWorkspaceIdentifier({ id: 'id', folders: [] });
assert.ok(!identifier);
assert.ok(!isSingleFolderWorkspaceIdentifier(identifier));
assert.ok(!isWorkspaceIdentifier(identifier));
identifier = toWorkspaceIdentifier({ id: 'id', folders: [{ index: 0, name: 'test', toResource: () => URI.file('test'), uri: URI.file('test') }] });
assert.ok(identifier);
assert.ok(isSingleFolderWorkspaceIdentifier(identifier));
assert.ok(!isWorkspaceIdentifier(identifier));
identifier = toWorkspaceIdentifier({ id: 'id', configuration: URI.file('test.code-workspace'), folders: [] });
assert.ok(identifier);
assert.ok(!isSingleFolderWorkspaceIdentifier(identifier));
assert.ok(isWorkspaceIdentifier(identifier));
});
});
......@@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path';
import * as pfs from 'vs/base/node/pfs';
import { EnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { WorkspacesMainService, IStoredWorkspace, getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { NullLogService } from 'vs/platform/log/common/log';
import { URI } from 'vs/base/common/uri';
......@@ -105,7 +105,7 @@ export class TestBackupMainService implements IBackupMainService {
}
suite('WorkspacesMainService', () => {
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesservice');
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesmainservice');
const untitledWorkspacesHomePath = path.join(parentDir, 'Workspaces');
class TestEnvironmentService extends EnvironmentMainService {
......@@ -147,13 +147,13 @@ suite('WorkspacesMainService', () => {
service = new WorkspacesMainService(environmentService, logService, new TestBackupMainService(), new TestDialogMainService());
// Delete any existing backups completely and then re-create it.
await pfs.rimraf(untitledWorkspacesHomePath);
await pfs.rimraf(parentDir);
return pfs.mkdirp(untitledWorkspacesHomePath);
});
teardown(() => {
return pfs.rimraf(untitledWorkspacesHomePath);
return pfs.rimraf(parentDir);
});
function assertPathEquals(p1: string, p2: string): void {
......@@ -448,4 +448,40 @@ suite('WorkspacesMainService', () => {
untitled = service.getUntitledWorkspacesSync();
assert.equal(0, untitled.length);
});
test('getSingleWorkspaceIdentifier', async function () {
const nonLocalUri = URI.parse('myscheme://server/work/p/f1');
const nonLocalUriId = getSingleFolderWorkspaceIdentifier(nonLocalUri);
assert.ok(nonLocalUriId?.id);
const localNonExistingUri = URI.file(path.join(parentDir, 'f1'));
const localNonExistingUriId = getSingleFolderWorkspaceIdentifier(localNonExistingUri);
assert.ok(!localNonExistingUriId);
fs.mkdirSync(path.join(parentDir, 'f1'));
const localExistingUri = URI.file(path.join(parentDir, 'f1'));
const localExistingUriId = getSingleFolderWorkspaceIdentifier(localExistingUri);
assert.ok(localExistingUriId?.id);
});
test('workspace identifiers are stable', function () {
// workspace identifier (local)
assert.strictEqual(getWorkspaceIdentifier(URI.file('/hello/test')).id, 'e36736311be12ff6d695feefe415b3e8');
// single folder identifier (local)
const fakeStat = {
ino: 1611312115129,
birthtimeMs: 1611312115129,
birthtime: new Date(1611312115129)
};
assert.strictEqual(getSingleFolderWorkspaceIdentifier(URI.file('/hello/test'), fakeStat as fs.Stats)?.id, '1d726b3d516dc2a6d343abf4797eaaef');
// workspace identifier (remote)
assert.strictEqual(getWorkspaceIdentifier(URI.parse('vscode-remote:/hello/test')).id, '786de4f224d57691f218dc7f31ee2ee3');
// single folder identifier (remote)
assert.strictEqual(getSingleFolderWorkspaceIdentifier(URI.parse('vscode-remote:/hello/test'))?.id, '786de4f224d57691f218dc7f31ee2ee3');
});
});
......@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { mark } from 'vs/base/common/performance';
import { hash } from 'vs/base/common/hash';
import { domContentLoaded, detectFullscreen, getCookieValue } from 'vs/base/browser/dom';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILogService, ConsoleLogService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
......@@ -40,7 +39,7 @@ import { BufferLogService } from 'vs/platform/log/common/bufferLog';
import { FileLogService } from 'vs/platform/log/common/fileLogService';
import { toLocalISOString } from 'vs/base/common/date';
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
import { coalesce } from 'vs/base/common/arrays';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { ICommandService } from 'vs/platform/commands/common/commands';
......@@ -142,7 +141,7 @@ class BrowserMain extends Disposable {
// CONTRIBUTE IT VIA WORKBENCH.WEB.MAIN.TS AND registerSingleton().
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
const payload = await this.resolveWorkspaceInitializationPayload();
const payload = this.resolveWorkspaceInitializationPayload();
// Product
const productService: IProductService = { _serviceBrand: undefined, ...product, ...this.configuration.productConfiguration };
......@@ -332,7 +331,7 @@ class BrowserMain extends Disposable {
}
}
private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
let workspace: IWorkspace | undefined = undefined;
if (this.configuration.workspaceProvider) {
workspace = this.configuration.workspaceProvider.workspace;
......@@ -345,8 +344,7 @@ class BrowserMain extends Disposable {
// Single-folder workspace
if (workspace && isFolderToOpen(workspace)) {
const id = hash(workspace.folderUri.toString()).toString(16);
return { id, folder: workspace.folderUri };
return getSingleFolderWorkspaceIdentifier(workspace.folderUri);
}
return { id: 'empty-window' };
......
......@@ -28,6 +28,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IDownloadService } from 'vs/platform/download/common/download';
import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand, RemoteFileDialogContext } from 'vs/workbench/services/dialogs/browser/simpleFileDialog';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
class RemoteChannelsContribution implements IWorkbenchContribution {
......@@ -124,6 +125,7 @@ class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchC
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IConfigurationService configurationService: IConfigurationService,
@ICommandService commandService: ICommandService,
@IWorkspaceContextService contextService: IWorkspaceContextService
) {
super();
......@@ -136,8 +138,8 @@ class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchC
return shouldShowExplorer();
}
const { remoteAuthority, folderUri, workspace, filesToDiff, filesToOpenOrCreate, filesToWait } = environmentService.configuration;
if (remoteAuthority && !folderUri && !workspace && !filesToDiff?.length && !filesToOpenOrCreate?.length && !filesToWait) {
const { remoteAuthority, filesToDiff, filesToOpenOrCreate, filesToWait } = environmentService.configuration;
if (remoteAuthority && contextService.getWorkbenchState() === WorkbenchState.EMPTY && !filesToDiff?.length && !filesToOpenOrCreate?.length && !filesToWait) {
remoteAuthorityResolverService.resolveAuthority(remoteAuthority).then(() => {
if (shouldShowExplorer()) {
commandService.executeCommand('workbench.view.explorer');
......
......@@ -5,9 +5,6 @@
import * as fs from 'fs';
import { gracefulify } from 'graceful-fs';
import { createHash } from 'crypto';
import { exists, stat } from 'vs/base/node/pfs';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
import { mark } from 'vs/base/common/performance';
import { Workbench } from 'vs/workbench/browser/workbench';
......@@ -21,11 +18,10 @@ import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environ
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier, IWorkspaceIdentifier, IMultiFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILogService } from 'vs/platform/log/common/log';
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
import { Schemas } from 'vs/base/common/network';
import { sanitizeFilePath } from 'vs/base/common/extpath';
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
......@@ -83,14 +79,11 @@ class DesktopMain extends Disposable {
}
private reviveUris() {
if (this.configuration.folderUri) {
this.configuration.folderUri = URI.revive(this.configuration.folderUri);
}
if (this.configuration.workspace) {
this.configuration.workspace = reviveWorkspaceIdentifier(this.configuration.workspace);
}
// Workspace
this.configuration.workspace = reviveIdentifier(this.configuration.workspace);
// Files
const filesToWait = this.configuration.filesToWait;
const filesToWaitPaths = filesToWait?.paths;
[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
......@@ -238,7 +231,7 @@ class DesktopMain extends Disposable {
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
}
const payload = await this.resolveWorkspaceInitializationPayload();
const payload = this.resolveWorkspaceInitializationPayload();
const services = await Promise.all([
this.createWorkspaceService(payload, fileService, remoteAgentService, uriIdentityService, logService).then(service => {
......@@ -286,18 +279,8 @@ class DesktopMain extends Disposable {
return { serviceCollection, logService, storageService: services[1] };
}
private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined;
// Multi-root workspace
if (this.configuration.workspace) {
workspaceInitializationPayload = await this.resolveMultiFolderWorkspaceInitializationPayload(this.configuration.workspace);
}
// Single-folder workspace
else if (this.configuration.folderUri) {
workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.configuration.folderUri);
}
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace;
// Fallback to empty workspace if we have no payload yet.
if (!workspaceInitializationPayload) {
......@@ -316,61 +299,6 @@ class DesktopMain extends Disposable {
return workspaceInitializationPayload;
}
private async resolveMultiFolderWorkspaceInitializationPayload(workspace: IWorkspaceIdentifier): Promise<IMultiFolderWorkspaceInitializationPayload | undefined> {
// It is possible that the workspace file does not exist
// on disk anymore, so we return `undefined` in that case
// (https://github.com/microsoft/vscode/issues/110982)
if (workspace.configPath.scheme === Schemas.file && !await exists(workspace.configPath.fsPath)) {
return undefined;
}
return workspace;
}
private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
try {
const folder = folderUri.scheme === Schemas.file
? URI.file(sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd())) // For local: ensure path is absolute
: folderUri;
return {
id: await this.createHash(folderUri),
folder
};
} catch (error) {
onUnexpectedError(error);
}
return;
}
private async createHash(resource: URI): Promise<string> {
// Return early the folder is not local
if (resource.scheme !== Schemas.file) {
return createHash('md5').update(resource.toString()).digest('hex');
}
const fileStat = await stat(resource.fsPath);
let ctime: number | undefined;
if (isLinux) {
ctime = fileStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
} else if (isMacintosh) {
ctime = fileStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
} else if (isWindows) {
if (typeof fileStat.birthtimeMs === 'number') {
ctime = Math.floor(fileStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
} else {
ctime = fileStat.birthtime.getTime();
}
}
// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
// deleted and recreated. in that case we do not want to carry over previous state
return createHash('md5').update(resource.fsPath).update(ctime ? String(ctime) : '').digest('hex');
}
private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
const workspaceService = new WorkspaceService({ remoteAuthority: this.environmentService.remoteAuthority, configurationCache: new ConfigurationCache(URI.file(this.environmentService.userDataPath), fileService) }, this.environmentService, fileService, remoteAgentService, uriIdentityService, logService);
......
......@@ -20,6 +20,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { Codicon } from 'vs/base/common/codicons';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export class CloseCurrentWindowAction extends Action {
......@@ -154,8 +155,8 @@ export abstract class BaseSwitchWindow extends Action {
const windows = await this.nativeHostService.getWindows();
const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to");
const picks = windows.map(window => {
const resource = window.filename ? URI.file(window.filename) : window.folderUri ? window.folderUri : window.workspace ? window.workspace.configPath : undefined;
const fileKind = window.filename ? FileKind.FILE : window.workspace ? FileKind.ROOT_FOLDER : window.folderUri ? FileKind.FOLDER : FileKind.FILE;
const resource = window.filename ? URI.file(window.filename) : isSingleFolderWorkspaceIdentifier(window.workspace) ? window.workspace.uri : isWorkspaceIdentifier(window.workspace) ? window.workspace.configPath : undefined;
const fileKind = window.filename ? FileKind.FILE : isSingleFolderWorkspaceIdentifier(window.workspace) ? FileKind.FOLDER : isWorkspaceIdentifier(window.workspace) ? FileKind.ROOT_FOLDER : FileKind.FILE;
return {
payload: window.id,
label: window.title,
......
......@@ -12,7 +12,7 @@ import { domContentLoaded } from 'vs/base/browser/dom';
import { URI } from 'vs/base/common/uri';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
......@@ -62,14 +62,11 @@ class DesktopMain extends Disposable {
}
private reviveUris() {
if (this.configuration.folderUri) {
this.configuration.folderUri = URI.revive(this.configuration.folderUri);
}
if (this.configuration.workspace) {
this.configuration.workspace = reviveWorkspaceIdentifier(this.configuration.workspace);
}
// Workspace
this.configuration.workspace = reviveIdentifier(this.configuration.workspace);
// Files
const filesToWait = this.configuration.filesToWait;
const filesToWaitPaths = filesToWait?.paths;
[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
......
......@@ -173,7 +173,7 @@ export class SimpleWorkspaceService implements IWorkspaceContextService {
getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return resource && resource.scheme === workspaceResource.scheme ? this.workspace.folders[0] : null; }
isInsideWorkspace(resource: URI): boolean { return resource && resource.scheme === workspaceResource.scheme; }
isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { return true; }
isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean { return true; }
}
//#endregion
......
......@@ -17,7 +17,7 @@ import { Configuration } from 'vs/workbench/services/configuration/common/config
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration';
......@@ -177,12 +177,19 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
return !!this.getWorkspaceFolder(resource);
}
public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
public isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean {
switch (this.getWorkbenchState()) {
case WorkbenchState.FOLDER:
return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && this.uriIdentityService.extUri.isEqual(workspaceIdentifier, this.workspace.folders[0].uri);
let folderUri: URI | undefined = undefined;
if (URI.isUri(workspaceIdOrFolder)) {
folderUri = workspaceIdOrFolder;
} else if (isSingleFolderWorkspaceIdentifier(workspaceIdOrFolder)) {
folderUri = workspaceIdOrFolder.uri;
}
return URI.isUri(folderUri) && this.uriIdentityService.extUri.isEqual(folderUri, this.workspace.folders[0].uri);
case WorkbenchState.WORKSPACE:
return isWorkspaceIdentifier(workspaceIdentifier) && this.workspace.id === workspaceIdentifier.id;
return isWorkspaceIdentifier(workspaceIdOrFolder) && this.workspace.id === workspaceIdOrFolder.id;
}
return false;
}
......@@ -386,7 +393,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
return this.createMultiFolderWorkspace(arg);
}
if (isSingleFolderWorkspaceInitializationPayload(arg)) {
if (isSingleFolderWorkspaceIdentifier(arg)) {
return this.createSingleFolderWorkspace(arg);
}
......@@ -405,14 +412,14 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
});
}
private createSingleFolderWorkspace(singleFolder: ISingleFolderWorkspaceInitializationPayload): Promise<Workspace> {
const workspace = new Workspace(singleFolder.id, [toWorkspaceFolder(singleFolder.folder)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Promise<Workspace> {
const workspace = new Workspace(singleFolderWorkspaceIdentifier.id, [toWorkspaceFolder(singleFolderWorkspaceIdentifier.uri)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = true;
return Promise.resolve(workspace);
}
private createEmptyWorkspace(emptyWorkspace: IEmptyWorkspaceInitializationPayload): Promise<Workspace> {
const workspace = new Workspace(emptyWorkspace.id, [], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
private createEmptyWorkspace(emptyWorkspacePayload: IEmptyWorkspaceInitializationPayload): Promise<Workspace> {
const workspace = new Workspace(emptyWorkspacePayload.id, [], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = true;
return Promise.resolve(workspace);
}
......
......@@ -39,7 +39,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache';
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl';
import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { hash } from 'vs/base/common/hash';
import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
......@@ -94,7 +94,7 @@ suite('ConfigurationEditingService', () => {
workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService()));
instantiationService.stub(IWorkspaceContextService, workspaceService);
await workspaceService.initialize({ folder: workspaceFolder, id: hash(workspaceFolder.toString()).toString(16) });
await workspaceService.initialize(getSingleFolderWorkspaceIdentifier(workspaceFolder));
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IKeybindingEditingService, disposables.add(instantiationService.createInstance(KeybindingsEditingService)));
instantiationService.stub(ITextFileService, disposables.add(instantiationService.createInstance(TestTextFileService)));
......
......@@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { ISingleFolderWorkspaceInitializationPayload, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
......@@ -47,11 +47,11 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
import { hash } from 'vs/base/common/hash';
function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceInitializationPayload {
function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier {
return {
id: hash(folder.toString()).toString(16),
folder
} as ISingleFolderWorkspaceInitializationPayload;
uri: folder
};
}
class ConfigurationCache extends BrowserConfigurationCache {
......
......@@ -25,6 +25,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { BeforeShutdownEvent, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
/**
* A workspace to open in the workbench can either be:
......@@ -338,7 +339,7 @@ export class BrowserHostService extends Disposable implements IHostService {
}
if (isWorkspaceToOpen(openable)) {
return this.labelService.getWorkspaceLabel({ id: '', configPath: openable.workspaceUri }, { verbose: true });
return this.labelService.getWorkspaceLabel(getWorkspaceIdentifier(openable.workspaceUri), { verbose: true });
}
return this.labelService.getUriLabel(openable.fileUri);
......
......@@ -14,7 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources';
import { tildify, getPathLabel } from 'vs/base/common/labels';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { match } from 'vs/base/common/glob';
......@@ -180,44 +180,60 @@ export class LabelService extends Disposable implements ILabelService {
return paths.basename(label);
}
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string {
getWorkspaceLabel(workspace: IWorkspace | IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, options?: { verbose: boolean }): string {
if (IWorkspace.isIWorkspace(workspace)) {
const identifier = toWorkspaceIdentifier(workspace);
if (!identifier) {
return '';
if (identifier) {
return this.getWorkspaceLabel(identifier, options);
}
workspace = identifier;
return '';
}
// Workspace: Single Folder
// Workspace: Single Folder (as URI)
if (URI.isUri(workspace)) {
return this.doGetSingleFolderWorkspaceLabel(workspace, options);
}
// Workspace: Single Folder (as workspace identifier)
if (isSingleFolderWorkspaceIdentifier(workspace)) {
// Folder on disk
const label = options && options.verbose ? this.getUriLabel(workspace) : basename(workspace) || '/';
return this.appendWorkspaceSuffix(label, workspace);
return this.doGetSingleFolderWorkspaceLabel(workspace.uri, options);
}
// Workspace: Multi Root
if (isWorkspaceIdentifier(workspace)) {
// Workspace: Untitled
if (isUntitledWorkspace(workspace.configPath, this.environmentService)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: Saved
let filename = basename(workspace.configPath);
if (filename.endsWith(WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
let label;
if (options && options.verbose) {
label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), filename)));
} else {
label = localize('workspaceName', "{0} (Workspace)", filename);
}
return this.appendWorkspaceSuffix(label, workspace.configPath);
return this.doGetWorkspaceLabel(workspace.configPath, options);
}
return '';
}
private doGetWorkspaceLabel(workspaceUri: URI, options?: { verbose: boolean }): string {
// Workspace: Untitled
if (isUntitledWorkspace(workspaceUri, this.environmentService)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: Saved
let filename = basename(workspaceUri);
if (filename.endsWith(WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
let label;
if (options && options.verbose) {
label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspaceUri), filename)));
} else {
label = localize('workspaceName', "{0} (Workspace)", filename);
}
return this.appendWorkspaceSuffix(label, workspaceUri);
}
private doGetSingleFolderWorkspaceLabel(folderUri: URI, options?: { verbose: boolean }): string {
const label = options && options.verbose ? this.getUriLabel(folderUri) : basename(folderUri) || '/';
return this.appendWorkspaceSuffix(label, folderUri);
}
getSeparator(scheme: string, authority?: string): '/' | '\\' {
......
......@@ -3,13 +3,32 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { hash } from 'vs/base/common/hash';
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
export function getWorkspaceIdentifier(workspacePath: URI): IWorkspaceIdentifier {
return {
id: hash(workspacePath.toString()).toString(16),
id: getWorkspaceId(workspacePath),
configPath: workspacePath
};
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
export function getSingleFolderWorkspaceIdentifier(folderPath: URI): ISingleFolderWorkspaceIdentifier {
return {
id: getWorkspaceId(folderPath),
uri: folderPath
};
}
function getWorkspaceId(uri: URI): string {
return hash(uri.toString()).toString(16);
}
......@@ -8,7 +8,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
import { URI } from 'vs/base/common/uri';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspacesService, isUntitledWorkspace, IWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesService, isUntitledWorkspace, IWorkspaceIdentifier, hasWorkspaceFileExtension, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
......@@ -144,7 +144,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi
const windows = await this.nativeHostService.getWindows();
// Prevent overwriting a workspace that is currently opened in another window
if (windows.some(window => !!window.workspace && this.uriIdentityService.extUri.isEqual(window.workspace.configPath, path))) {
if (windows.some(window => isWorkspaceIdentifier(window.workspace) && this.uriIdentityService.extUri.isEqual(window.workspace.configPath, path))) {
await this.dialogService.show(
Severity.Info,
localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { getWorkspaceIdentifier, getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
suite('Workspaces', () => {
test('workspace identifiers are stable', function () {
// workspace identifier
assert.strictEqual(getWorkspaceIdentifier(URI.parse('vscode-remote:/hello/test')).id, '474434e4');
// single folder identifier
assert.strictEqual(getSingleFolderWorkspaceIdentifier(URI.parse('vscode-remote:/hello/test'))?.id, '474434e4');
});
});
......@@ -10,7 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceContextService, IWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { InMemoryStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage';
......@@ -114,8 +114,8 @@ export class TestContextService implements IWorkspaceContextService {
return URI.file(join('C:\\', workspaceRelativePath));
}
isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && resources.isEqual(this.workspace.folders[0].uri, workspaceIdentifier);
isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean {
return URI.isUri(workspaceIdOrFolder) && resources.isEqual(this.workspace.folders[0].uri, workspaceIdOrFolder);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册