chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 deletions

View File

@@ -3,18 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { coalesce } from 'vs/base/common/arrays';
import { IStateService } from 'vs/platform/state/node/state';
import { app, JumpListCategory } from 'electron';
import { app, JumpListCategory, JumpListItem } from 'electron';
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 { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IWorkspaceIdentifier, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile, toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData, WORKSPACE_EXTENSION, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
import { ThrottledDelayer } from 'vs/base/common/async';
import { isEqual, dirname, originalFSPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { dirname, originalFSPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
@@ -57,15 +57,15 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
declare readonly _serviceBrand: undefined;
private readonly _onRecentlyOpenedChange = new Emitter<void>();
private readonly _onRecentlyOpenedChange = this._register(new Emitter<void>());
readonly onRecentlyOpenedChange: CommonEvent<void> = this._onRecentlyOpenedChange.event;
private macOSRecentDocumentsUpdater = this._register(new ThrottledDelayer<void>(800));
private readonly macOSRecentDocumentsUpdater = this._register(new ThrottledDelayer<void>(800));
constructor(
@IStateService private readonly stateService: IStateService,
@ILogService private readonly logService: ILogService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService,
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService
) {
@@ -80,7 +80,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList());
// Add to history when entering workspace
this._register(this.workspacesMainService.onWorkspaceEntered(event => this.addRecentlyOpened([{ workspace: event.workspace }])));
this._register(this.workspacesManagementMainService.onWorkspaceEntered(event => this.addRecentlyOpened([{ workspace: event.workspace }])));
}
private handleWindowsJumpList(): void {
@@ -89,40 +89,40 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
}
this.updateWindowsJumpList();
this.onRecentlyOpenedChange(() => this.updateWindowsJumpList());
this._register(this.onRecentlyOpenedChange(() => this.updateWindowsJumpList()));
}
addRecentlyOpened(newlyAdded: IRecent[]): void {
addRecentlyOpened(recentToAdd: IRecent[]): void {
const workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
const files: IRecentFile[] = [];
for (let curr of newlyAdded) {
for (let recent of recentToAdd) {
// Workspace
if (isRecentWorkspace(curr)) {
if (!this.workspacesMainService.isUntitledWorkspace(curr.workspace) && indexOfWorkspace(workspaces, curr.workspace) === -1) {
workspaces.push(curr);
if (isRecentWorkspace(recent)) {
if (!this.workspacesManagementMainService.isUntitledWorkspace(recent.workspace) && indexOfWorkspace(workspaces, recent.workspace) === -1) {
workspaces.push(recent);
}
}
// Folder
else if (isRecentFolder(curr)) {
if (indexOfFolder(workspaces, curr.folderUri) === -1) {
workspaces.push(curr);
else if (isRecentFolder(recent)) {
if (indexOfFolder(workspaces, recent.folderUri) === -1) {
workspaces.push(recent);
}
}
// File
else {
const alreadyExistsInHistory = indexOfFile(files, curr.fileUri) >= 0;
const shouldBeFiltered = curr.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(curr.fileUri)) >= 0;
const alreadyExistsInHistory = indexOfFile(files, recent.fileUri) >= 0;
const shouldBeFiltered = recent.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(recent.fileUri)) >= 0;
if (!alreadyExistsInHistory && !shouldBeFiltered) {
files.push(curr);
files.push(recent);
// Add to recent documents (Windows only, macOS later)
if (isWindows && curr.fileUri.scheme === Schemas.file) {
app.addRecentDocument(curr.fileUri.fsPath);
if (isWindows && recent.fileUri.scheme === Schemas.file) {
app.addRecentDocument(recent.fileUri.fsPath);
}
}
}
@@ -147,14 +147,15 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
}
}
removeRecentlyOpened(toRemove: URI[]): void {
removeRecentlyOpened(recentToRemove: URI[]): void {
const keep = (recent: IRecent) => {
const uri = location(recent);
for (const resource of toRemove) {
if (isEqual(resource, uri)) {
for (const resourceToRemove of recentToRemove) {
if (extUriBiasedIgnorePathCase.isEqual(resourceToRemove, uri)) {
return false;
}
}
return true;
};
@@ -246,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.workspacesManagementMainService.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
@@ -319,8 +317,8 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
items: [
{
type: 'task',
title: nls.localize('newWindow', "New Window"),
description: nls.localize('newWindowDesc', "Opens a new window"),
title: localize('newWindow', "New Window"),
description: localize('newWindowDesc', "Opens a new window"),
program: process.execPath,
args: '-n', // force new window
iconPath: process.execPath,
@@ -330,57 +328,59 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
});
// Recent Workspaces
try {
if (this.getRecentlyOpened().workspaces.length > 0) {
if (this.getRecentlyOpened().workspaces.length > 0) {
// The user might have meanwhile removed items from the jump list and we have to respect that
// so we need to update our list of recent paths with the choice of the user to not add them again
// Also: Windows will not show our custom category at all if there is any entry which was removed
// by the user! See https://github.com/microsoft/vscode/issues/15052
let toRemove: URI[] = [];
for (let item of app.getJumpListSettings().removedItems) {
const args = item.args;
if (args) {
const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args);
if (match) {
toRemove.push(URI.parse(match[2]));
}
// The user might have meanwhile removed items from the jump list and we have to respect that
// so we need to update our list of recent paths with the choice of the user to not add them again
// Also: Windows will not show our custom category at all if there is any entry which was removed
// by the user! See https://github.com/microsoft/vscode/issues/15052
let toRemove: URI[] = [];
for (let item of app.getJumpListSettings().removedItems) {
const args = item.args;
if (args) {
const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args);
if (match) {
toRemove.push(URI.parse(match[2]));
}
}
this.removeRecentlyOpened(toRemove);
}
this.removeRecentlyOpened(toRemove);
// Add entries
// Add entries
let hasWorkspaces = false;
const items: JumpListItem[] = coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => {
const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri;
const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome);
let description;
let args;
if (URI.isUri(workspace)) {
description = localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService));
args = `--folder-uri "${workspace.toString()}"`;
} else {
hasWorkspaces = true;
description = localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService));
args = `--file-uri "${workspace.configPath.toString()}"`;
}
return {
type: 'task',
title: title.substr(0, 255), // Windows seems to be picky around the length of entries
description: description.substr(0, 255), // (see https://github.com/microsoft/vscode/issues/111177)
program: process.execPath,
args,
iconPath: 'explorer.exe', // simulate folder icon
iconIndex: 0
};
}));
if (items.length > 0) {
jumpList.push({
type: 'custom',
name: nls.localize('recentFolders', "Recent Workspaces"),
items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => {
const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri;
const title = recent.label ? splitName(recent.label).name : this.getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome);
let description;
let args;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService));
args = `--folder-uri "${workspace.toString()}"`;
} else {
description = nls.localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService));
args = `--file-uri "${workspace.configPath.toString()}"`;
}
return {
type: 'task',
title,
description,
program: process.execPath,
args,
iconPath: 'explorer.exe', // simulate folder icon
iconIndex: 0
};
}))
name: hasWorkspaces ? localize('recentFoldersAndWorkspaces', "Recent Folders & Workspaces") : localize('recentFolders', "Recent Folders"),
items
});
}
} catch (error) {
this.logService.warn('updateWindowsJumpList#recentWorkspaces', error); // https://github.com/microsoft/vscode/issues/111177
}
// Recent
@@ -396,21 +396,24 @@ 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);
}
// Workspace: Untitled
if (extUriBiasedIgnorePathCase.isEqualOrParent(workspace.configPath, workspaceHome)) {
return nls.localize('untitledWorkspace', "Untitled (Workspace)");
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);
}
return nls.localize('workspaceName', "{0} (Workspace)", filename);
return localize('workspaceName', "{0} (Workspace)", filename);
}
}
@@ -430,10 +433,10 @@ 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 {
return arr.findIndex(folder => isRecentFolder(folder) && isEqual(folder.folderUri, candidate));
function indexOfFolder(arr: IRecent[], candidate: URI): number {
return arr.findIndex(folder => isRecentFolder(folder) && extUriBiasedIgnorePathCase.isEqual(folder.folderUri, candidate));
}
function indexOfFile(arr: IRecentFile[], candidate: URI): number {
return arr.findIndex(file => isEqual(file.fileUri, candidate));
return arr.findIndex(file => extUriBiasedIgnorePathCase.isEqual(file.fileUri, candidate));
}

View File

@@ -3,337 +3,79 @@
* 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 { 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 } 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';
import * as json from 'vs/base/common/json';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { AddFirstParameterToFunctions } from 'vs/base/common/types';
import { IWorkspacesService, IEnterWorkspaceResult, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { originalFSPath, joinPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { localize } from 'vs/nls';
import product from 'vs/platform/product/common/product';
import { MessageBoxOptions, BrowserWindow } from 'electron';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
import { findWindowOnWorkspace } from 'vs/platform/windows/node/window';
export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('workspacesMainService');
export interface IWorkspaceEnteredEvent {
window: ICodeWindow;
workspace: IWorkspaceIdentifier;
}
export interface IWorkspacesMainService {
readonly _serviceBrand: undefined;
readonly onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier>;
readonly onWorkspaceEntered: Event<IWorkspaceEnteredEvent>;
enterWorkspace(intoWindow: ICodeWindow, openedWindows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | null>;
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier;
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void;
getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[];
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean;
resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null;
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
}
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
remoteAuthority?: string;
}
export class WorkspacesMainService extends Disposable implements IWorkspacesMainService {
export class WorkspacesMainService implements AddFirstParameterToFunctions<IWorkspacesService, Promise<unknown> /* only methods, not events */, number /* window ID */> {
declare readonly _serviceBrand: undefined;
private readonly untitledWorkspacesHome: URI; // local URI that contains all untitled workspaces
private readonly _onUntitledWorkspaceDeleted = this._register(new Emitter<IWorkspaceIdentifier>());
readonly onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier> = this._onUntitledWorkspaceDeleted.event;
private readonly _onWorkspaceEntered = this._register(new Emitter<IWorkspaceEnteredEvent>());
readonly onWorkspaceEntered: Event<IWorkspaceEnteredEvent> = this._onWorkspaceEntered.event;
constructor(
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@ILogService private readonly logService: ILogService,
@IBackupMainService private readonly backupMainService: IBackupMainService,
@IDialogMainService private readonly dialogMainService: IDialogMainService
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService,
@IBackupMainService private readonly backupMainService: IBackupMainService
) {
super();
this.untitledWorkspacesHome = environmentService.untitledWorkspacesHome;
}
resolveLocalWorkspaceSync(uri: URI): IResolvedWorkspace | null {
if (!this.isWorkspacePath(uri)) {
return null; // does not look like a valid workspace config file
}
if (uri.scheme !== Schemas.file) {
return null;
}
//#region Workspace Management
let contents: string;
try {
contents = readFileSync(uri.fsPath, 'utf8');
} catch (error) {
return null; // invalid workspace
}
return this.doResolveWorkspace(uri, contents);
}
private isWorkspacePath(uri: URI): boolean {
return isUntitledWorkspace(uri, this.environmentService) || hasWorkspaceFileExtension(uri);
}
private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null {
try {
const workspace = this.doParseStoredWorkspace(path, contents);
const workspaceIdentifier = getWorkspaceIdentifier(path);
return {
id: workspaceIdentifier.id,
configPath: workspaceIdentifier.configPath,
folders: toWorkspaceFolders(workspace.folders, workspaceIdentifier.configPath),
remoteAuthority: workspace.remoteAuthority
};
} catch (error) {
this.logService.warn(error.toString());
async enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | null> {
const window = this.windowsMainService.getWindowById(windowId);
if (window) {
return this.workspacesManagementMainService.enterWorkspace(window, this.windowsMainService.getWindows(), path);
}
return null;
}
private doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace {
// Parse workspace file
let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser
// Filter out folders which do not have a path or uri set
if (storedWorkspace && Array.isArray(storedWorkspace.folders)) {
storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder));
} else {
throw new Error(`${path.toString(true)} looks like an invalid workspace file.`);
}
return storedWorkspace;
createUntitledWorkspace(windowId: number, folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
return this.workspacesManagementMainService.createUntitledWorkspace(folders, remoteAuthority);
}
async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
const configPath = workspace.configPath.fsPath;
await mkdirp(dirname(configPath));
await writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t'));
return workspace;
deleteUntitledWorkspace(windowId: number, workspace: IWorkspaceIdentifier): Promise<void> {
return this.workspacesManagementMainService.deleteUntitledWorkspace(workspace);
}
createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): IWorkspaceIdentifier {
const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
const configPath = workspace.configPath.fsPath;
const configPathDir = dirname(configPath);
if (!existsSync(configPathDir)) {
const configPathDirDir = dirname(configPathDir);
if (!existsSync(configPathDirDir)) {
mkdirSync(configPathDirDir);
}
mkdirSync(configPathDir);
}
writeFileSync(configPath, JSON.stringify(storedWorkspace, null, '\t'));
return workspace;
getWorkspaceIdentifier(windowId: number, workspacePath: URI): Promise<IWorkspaceIdentifier> {
return this.workspacesManagementMainService.getWorkspaceIdentifier(workspacePath);
}
private newUntitledWorkspace(folders: IWorkspaceFolderCreationData[] = [], remoteAuthority?: string): { workspace: IWorkspaceIdentifier, storedWorkspace: IStoredWorkspace } {
const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString();
const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId);
const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME);
//#endregion
const storedWorkspaceFolder: IStoredWorkspaceFolder[] = [];
//#region Workspaces History
for (const folder of folders) {
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder));
}
readonly onRecentlyOpenedChange = this.workspacesHistoryMainService.onRecentlyOpenedChange;
return {
workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath),
storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority }
};
async getRecentlyOpened(windowId: number): Promise<IRecentlyOpened> {
return this.workspacesHistoryMainService.getRecentlyOpened(this.windowsMainService.getWindowById(windowId));
}
async getWorkspaceIdentifier(configPath: URI): Promise<IWorkspaceIdentifier> {
return getWorkspaceIdentifier(configPath);
async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise<void> {
return this.workspacesHistoryMainService.addRecentlyOpened(recents);
}
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean {
return isUntitledWorkspace(workspace.configPath, this.environmentService);
async removeRecentlyOpened(windowId: number, paths: URI[]): Promise<void> {
return this.workspacesHistoryMainService.removeRecentlyOpened(paths);
}
deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
if (!this.isUntitledWorkspace(workspace)) {
return; // only supported for untitled workspaces
}
// Delete from disk
this.doDeleteUntitledWorkspaceSync(workspace);
// Event
this._onUntitledWorkspaceDeleted.fire(workspace);
async clearRecentlyOpened(windowId: number): Promise<void> {
return this.workspacesHistoryMainService.clearRecentlyOpened();
}
async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
this.deleteUntitledWorkspaceSync(workspace);
//#endregion
//#region Dirty Workspaces
async getDirtyWorkspaces(): Promise<Array<IWorkspaceIdentifier | URI>> {
return this.backupMainService.getDirtyWorkspaces();
}
private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
const configPath = originalFSPath(workspace.configPath);
try {
// Delete Workspace
rimrafSync(dirname(configPath));
// Mark Workspace Storage to be deleted
const workspaceStoragePath = join(this.environmentService.workspaceStorageHome.fsPath, workspace.id);
if (existsSync(workspaceStoragePath)) {
writeFileSync(join(workspaceStoragePath, 'obsolete'), '');
}
} catch (error) {
this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`);
}
}
getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[] {
let untitledWorkspaces: IUntitledWorkspaceInfo[] = [];
try {
const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME));
for (const untitledWorkspacePath of untitledWorkspacePaths) {
const workspace = getWorkspaceIdentifier(untitledWorkspacePath);
const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath);
if (!resolvedWorkspace) {
this.doDeleteUntitledWorkspaceSync(workspace);
} else {
untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority });
}
}
} catch (error) {
if (error.code !== 'ENOENT') {
this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`);
}
}
return untitledWorkspaces;
}
async enterWorkspace(window: ICodeWindow, windows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | null> {
if (!window || !window.win || !window.isReady) {
return null; // return early if the window is not ready or disposed
}
const isValid = await this.isValidTargetWorkspacePath(window, windows, path);
if (!isValid) {
return null; // return early if the workspace is not valid
}
const result = this.doEnterWorkspace(window, getWorkspaceIdentifier(path));
if (!result) {
return null;
}
// Emit as event
this._onWorkspaceEntered.fire({ window, workspace: result.workspace });
return result;
}
private async isValidTargetWorkspacePath(window: ICodeWindow, windows: ICodeWindow[], path?: URI): Promise<boolean> {
if (!path) {
return true;
}
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, path)) {
return false; // window is already opened on a workspace with that path
}
// Prevent overwriting a workspace that is currently opened in another window
if (findWindowOnWorkspace(windows, getWorkspaceIdentifier(path))) {
const options: MessageBoxOptions = {
title: product.nameLong,
type: 'info',
buttons: [localize('ok', "OK")],
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
noLink: true
};
await this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
return false;
}
return true; // OK
}
private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult | null {
if (!window.config) {
return null;
}
window.focus();
// Register window for backups and migrate current backups over
let backupPath: string | undefined;
if (!window.config.extensionDevelopmentPath) {
backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
}
// if the window was opened on an untitled workspace, delete it.
if (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;
return { workspace, backupPath };
}
}
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');
}
export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier {
return {
configPath,
id: getWorkspaceId(configPath)
};
//#endregion
}

View File

@@ -0,0 +1,394 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { toWorkspaceFolders, 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, 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';
import { parse } from 'vs/base/common/json';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { originalFSPath, joinPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { localize } from 'vs/nls';
import product from 'vs/platform/product/common/product';
import { MessageBoxOptions, BrowserWindow } from 'electron';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
import { findWindowOnWorkspaceOrFolder } from 'vs/platform/windows/electron-main/windowsFinder';
export const IWorkspacesManagementMainService = createDecorator<IWorkspacesManagementMainService>('workspacesManagementMainService');
export interface IWorkspaceEnteredEvent {
window: ICodeWindow;
workspace: IWorkspaceIdentifier;
}
export interface IWorkspacesManagementMainService {
readonly _serviceBrand: undefined;
readonly onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier>;
readonly onWorkspaceEntered: Event<IWorkspaceEnteredEvent>;
enterWorkspace(intoWindow: ICodeWindow, openedWindows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | null>;
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier;
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void;
getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[];
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean;
resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null;
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
}
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
remoteAuthority?: string;
}
export class WorkspacesManagementMainService extends Disposable implements IWorkspacesManagementMainService {
declare readonly _serviceBrand: undefined;
private readonly untitledWorkspacesHome = this.environmentService.untitledWorkspacesHome; // local URI that contains all untitled workspaces
private readonly _onUntitledWorkspaceDeleted = this._register(new Emitter<IWorkspaceIdentifier>());
readonly onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier> = this._onUntitledWorkspaceDeleted.event;
private readonly _onWorkspaceEntered = this._register(new Emitter<IWorkspaceEnteredEvent>());
readonly onWorkspaceEntered: Event<IWorkspaceEnteredEvent> = this._onWorkspaceEntered.event;
constructor(
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@ILogService private readonly logService: ILogService,
@IBackupMainService private readonly backupMainService: IBackupMainService,
@IDialogMainService private readonly dialogMainService: IDialogMainService
) {
super();
}
resolveLocalWorkspaceSync(uri: URI): IResolvedWorkspace | null {
if (!this.isWorkspacePath(uri)) {
return null; // does not look like a valid workspace config file
}
if (uri.scheme !== Schemas.file) {
return null;
}
let contents: string;
try {
contents = readFileSync(uri.fsPath, 'utf8');
} catch (error) {
return null; // invalid workspace
}
return this.doResolveWorkspace(uri, contents);
}
private isWorkspacePath(uri: URI): boolean {
return isUntitledWorkspace(uri, this.environmentService) || hasWorkspaceFileExtension(uri);
}
private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null {
try {
const workspace = this.doParseStoredWorkspace(path, contents);
const workspaceIdentifier = getWorkspaceIdentifier(path);
return {
id: workspaceIdentifier.id,
configPath: workspaceIdentifier.configPath,
folders: toWorkspaceFolders(workspace.folders, workspaceIdentifier.configPath, extUriBiasedIgnorePathCase),
remoteAuthority: workspace.remoteAuthority
};
} catch (error) {
this.logService.warn(error.toString());
}
return null;
}
private doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace {
// Parse workspace file
const storedWorkspace: IStoredWorkspace = parse(contents); // use fault tolerant parser
// Filter out folders which do not have a path or uri set
if (storedWorkspace && Array.isArray(storedWorkspace.folders)) {
storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder));
} else {
throw new Error(`${path.toString(true)} looks like an invalid workspace file.`);
}
return storedWorkspace;
}
async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
const configPath = workspace.configPath.fsPath;
await mkdirp(dirname(configPath));
await writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t'));
return workspace;
}
createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): IWorkspaceIdentifier {
const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
const configPath = workspace.configPath.fsPath;
const configPathDir = dirname(configPath);
if (!existsSync(configPathDir)) {
const configPathDirDir = dirname(configPathDir);
if (!existsSync(configPathDirDir)) {
mkdirSync(configPathDirDir);
}
mkdirSync(configPathDir);
}
writeFileSync(configPath, JSON.stringify(storedWorkspace, null, '\t'));
return workspace;
}
private newUntitledWorkspace(folders: IWorkspaceFolderCreationData[] = [], remoteAuthority?: string): { workspace: IWorkspaceIdentifier, storedWorkspace: IStoredWorkspace } {
const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString();
const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId);
const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME);
const storedWorkspaceFolder: IStoredWorkspaceFolder[] = [];
for (const folder of folders) {
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder, !isWindows, extUriBiasedIgnorePathCase));
}
return {
workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath),
storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority }
};
}
async getWorkspaceIdentifier(configPath: URI): Promise<IWorkspaceIdentifier> {
return getWorkspaceIdentifier(configPath);
}
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean {
return isUntitledWorkspace(workspace.configPath, this.environmentService);
}
deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
if (!this.isUntitledWorkspace(workspace)) {
return; // only supported for untitled workspaces
}
// Delete from disk
this.doDeleteUntitledWorkspaceSync(workspace);
// Event
this._onUntitledWorkspaceDeleted.fire(workspace);
}
async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
this.deleteUntitledWorkspaceSync(workspace);
}
private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
const configPath = originalFSPath(workspace.configPath);
try {
// Delete Workspace
rimrafSync(dirname(configPath));
// Mark Workspace Storage to be deleted
const workspaceStoragePath = join(this.environmentService.workspaceStorageHome.fsPath, workspace.id);
if (existsSync(workspaceStoragePath)) {
writeFileSync(join(workspaceStoragePath, 'obsolete'), '');
}
} catch (error) {
this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`);
}
}
getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[] {
const untitledWorkspaces: IUntitledWorkspaceInfo[] = [];
try {
const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME));
for (const untitledWorkspacePath of untitledWorkspacePaths) {
const workspace = getWorkspaceIdentifier(untitledWorkspacePath);
const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath);
if (!resolvedWorkspace) {
this.doDeleteUntitledWorkspaceSync(workspace);
} else {
untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority });
}
}
} catch (error) {
if (error.code !== 'ENOENT') {
this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`);
}
}
return untitledWorkspaces;
}
async enterWorkspace(window: ICodeWindow, windows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | null> {
if (!window || !window.win || !window.isReady) {
return null; // return early if the window is not ready or disposed
}
const isValid = await this.isValidTargetWorkspacePath(window, windows, path);
if (!isValid) {
return null; // return early if the workspace is not valid
}
const result = this.doEnterWorkspace(window, getWorkspaceIdentifier(path));
if (!result) {
return null;
}
// Emit as event
this._onWorkspaceEntered.fire({ window, workspace: result.workspace });
return result;
}
private async isValidTargetWorkspacePath(window: ICodeWindow, windows: ICodeWindow[], workspacePath?: URI): Promise<boolean> {
if (!workspacePath) {
return true;
}
if (isWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, workspacePath)) {
return false; // window is already opened on a workspace with that path
}
// Prevent overwriting a workspace that is currently opened in another window
if (findWindowOnWorkspaceOrFolder(windows, workspacePath)) {
const options: MessageBoxOptions = {
title: product.nameLong,
type: 'info',
buttons: [localize('ok', "OK")],
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(workspacePath)),
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
noLink: true
};
await this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
return false;
}
return true; // OK
}
private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult | null {
if (!window.config) {
return null;
}
window.focus();
// Register window for backups and migrate current backups over
let backupPath: string | undefined;
if (!window.config.extensionDevelopmentPath) {
backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
}
// if the window was opened on an untitled workspace, delete it.
if (isWorkspaceIdentifier(window.openedWorkspace) && this.isUntitledWorkspace(window.openedWorkspace)) {
this.deleteUntitledWorkspaceSync(window.openedWorkspace);
}
// Update window configuration properly based on transition to workspace
window.config.workspace = workspace;
window.config.backupPath = backupPath;
return { workspace, backupPath };
}
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// 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 {
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
}

View File

@@ -1,81 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AddFirstParameterToFunctions } from 'vs/base/common/types';
import { IWorkspacesService, IEnterWorkspaceResult, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
export class WorkspacesService implements AddFirstParameterToFunctions<IWorkspacesService, Promise<unknown> /* only methods, not events */, number /* window ID */> {
declare readonly _serviceBrand: undefined;
constructor(
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService,
@IBackupMainService private readonly backupMainService: IBackupMainService
) {
}
//#region Workspace Management
async enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | null> {
const window = this.windowsMainService.getWindowById(windowId);
if (window) {
return this.workspacesMainService.enterWorkspace(window, this.windowsMainService.getWindows(), path);
}
return null;
}
createUntitledWorkspace(windowId: number, folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
return this.workspacesMainService.createUntitledWorkspace(folders, remoteAuthority);
}
deleteUntitledWorkspace(windowId: number, workspace: IWorkspaceIdentifier): Promise<void> {
return this.workspacesMainService.deleteUntitledWorkspace(workspace);
}
getWorkspaceIdentifier(windowId: number, workspacePath: URI): Promise<IWorkspaceIdentifier> {
return this.workspacesMainService.getWorkspaceIdentifier(workspacePath);
}
//#endregion
//#region Workspaces History
readonly onRecentlyOpenedChange = this.workspacesHistoryMainService.onRecentlyOpenedChange;
async getRecentlyOpened(windowId: number): Promise<IRecentlyOpened> {
return this.workspacesHistoryMainService.getRecentlyOpened(this.windowsMainService.getWindowById(windowId));
}
async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise<void> {
return this.workspacesHistoryMainService.addRecentlyOpened(recents);
}
async removeRecentlyOpened(windowId: number, paths: URI[]): Promise<void> {
return this.workspacesHistoryMainService.removeRecentlyOpened(paths);
}
async clearRecentlyOpened(windowId: number): Promise<void> {
return this.workspacesHistoryMainService.clearRecentlyOpened();
}
//#endregion
//#region Dirty Workspaces
async getDirtyWorkspaces(): Promise<Array<IWorkspaceIdentifier | URI>> {
return this.backupMainService.getDirtyWorkspaces();
}
//#endregion
}