Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -5,9 +5,9 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
import URI from 'vs/base/common/uri';
export const IWorkspaceEditingService = createDecorator<IWorkspaceEditingService>('workspaceEditingService');
@@ -16,14 +16,32 @@ export interface IWorkspaceEditingService {
_serviceBrand: ServiceIdentifier<any>;
/**
* add roots to the existing workspace
* Add folders to the existing workspace.
* When `donotNotifyError` is `true`, error will be bubbled up otherwise, the service handles the error with proper message and action
*/
addRoots(roots: URI[]): TPromise<void>;
addFolders(folders: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void>;
/**
* remove roots from the existing workspace
* Remove folders from the existing workspace
* When `donotNotifyError` is `true`, error will be bubbled up otherwise, the service handles the error with proper message and action
*/
removeRoots(roots: URI[]): TPromise<void>;
removeFolders(folders: URI[], donotNotifyError?: boolean): TPromise<void>;
/**
* creates a new workspace with the provided folders and opens it. if path is provided
* the workspace will be saved into that location.
*/
createAndEnterWorkspace(folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<void>;
/**
* saves the workspace to the provided path and opens it. requires a workspace to be opened.
*/
saveAndEnterWorkspace(path: string): TPromise<void>;
/**
* copies current workspace settings to the target workspace.
*/
copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): TPromise<void>;
}
export const IWorkspaceMigrationService = createDecorator<IWorkspaceMigrationService>('workspaceMigrationService');

View File

@@ -7,94 +7,244 @@
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import URI from 'vs/base/common/uri';
import { equals, distinct } from 'vs/base/common/arrays';
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspacesService, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { StorageService } from 'vs/platform/storage/common/storageService';
import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
import { IChoiceService, Severity, IMessageService } from 'vs/platform/message/common/message';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { distinct } from 'vs/base/common/arrays';
import { isLinux } from 'vs/base/common/platform';
import { dirname, relative } from 'path';
import { isEqualOrParent } from 'vs/base/common/paths';
import { isEqual } from 'vs/base/common/resources';
import { Action } from 'vs/base/common/actions';
import product from 'vs/platform/node/product';
export class WorkspaceEditingService implements IWorkspaceEditingService {
public _serviceBrand: any;
private static INFO_MESSAGE_KEY = 'enterWorkspace.message';
constructor(
@IJSONEditingService private jsonEditingService: IJSONEditingService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWindowsService private windowsService: IWindowsService,
@IWorkspacesService private workspacesService: IWorkspacesService
@IWorkspaceContextService private contextService: WorkspaceService,
@IWindowService private windowService: IWindowService,
@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
@IStorageService private storageService: IStorageService,
@IExtensionService private extensionService: IExtensionService,
@IBackupFileService private backupFileService: IBackupFileService,
@IChoiceService private choiceService: IChoiceService,
@IMessageService private messageService: IMessageService,
@ICommandService private commandService: ICommandService
) {
}
public addRoots(rootsToAdd: URI[]): TPromise<void> {
if (!this.isSupported()) {
return TPromise.as(void 0); // we need a workspace to begin with
public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): TPromise<void> {
const state = this.contextService.getWorkbenchState();
// If we are in no-workspace or single-folder workspace, adding folders has to
// enter a workspace.
if (state !== WorkbenchState.WORKSPACE) {
const newWorkspaceFolders: IWorkspaceFolderCreationData[] = distinct([
...this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData)),
...foldersToAdd
] as IWorkspaceFolderCreationData[], folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
return TPromise.as(void 0); // return if the operation is a no-op for the current state
}
return this.createAndEnterWorkspace(newWorkspaceFolders);
}
const roots = this.contextService.getWorkspace().roots;
return this.doSetRoots([...roots, ...rootsToAdd]);
// Delegate addition of folders to workspace service otherwise
return this.contextService.addFolders(foldersToAdd)
.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
}
public removeRoots(rootsToRemove: URI[]): TPromise<void> {
if (!this.isSupported()) {
return TPromise.as(void 0); // we need a workspace to begin with
public removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): TPromise<void> {
// If we are in single-folder state and the opened folder is to be removed,
// we close the workspace and enter the empty workspace state for the window.
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
const workspaceFolder = this.contextService.getWorkspace().folders[0];
if (foldersToRemove.some(folder => isEqual(folder, workspaceFolder.uri, !isLinux))) {
return this.windowService.closeWorkspace();
}
}
const roots = this.contextService.getWorkspace().roots;
const rootsToRemoveRaw = rootsToRemove.map(root => root.toString());
return this.doSetRoots(roots.filter(root => rootsToRemoveRaw.indexOf(root.toString()) === -1));
// Delegate removal of folders to workspace service otherwise
return this.contextService.removeFolders(foldersToRemove)
.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
}
private isSupported(): boolean {
// TODO@Ben multi root
return (
this.environmentService.appQuality !== 'stable' // not yet enabled in stable
&& this.contextService.hasMultiFolderWorkspace() // we need a multi folder workspace to begin with
);
public createAndEnterWorkspace(folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<void> {
return this.doEnterWorkspace(() => this.windowService.createAndEnterWorkspace(folders, path));
}
private doSetRoots(newRoots: URI[]): TPromise<void> {
const workspace = this.contextService.getWorkspace();
const currentWorkspaceRoots = this.contextService.getWorkspace().roots.map(root => root.fsPath);
const newWorkspaceRoots = this.validateRoots(newRoots);
public saveAndEnterWorkspace(path: string): TPromise<void> {
return this.doEnterWorkspace(() => this.windowService.saveAndEnterWorkspace(path));
}
// See if there are any changes
if (equals(currentWorkspaceRoots, newWorkspaceRoots)) {
return TPromise.as(void 0);
private handleWorkspaceConfigurationEditingError(error: JSONEditingError): TPromise<void> {
switch (error.code) {
case JSONEditingErrorCode.ERROR_INVALID_FILE:
return this.onInvalidWorkspaceConfigurationFileError();
case JSONEditingErrorCode.ERROR_FILE_DIRTY:
return this.onWorkspaceConfigurationFileDirtyError();
}
this.messageService.show(Severity.Error, error.message);
return TPromise.as(void 0);
}
// Apply to config
if (newWorkspaceRoots.length) {
const workspaceConfigFolder = dirname(workspace.configuration.fsPath);
const value: IStoredWorkspaceFolder[] = newWorkspaceRoots.map(newWorkspaceRoot => {
if (isEqualOrParent(newWorkspaceRoot, workspaceConfigFolder, !isLinux)) {
newWorkspaceRoot = relative(workspaceConfigFolder, newWorkspaceRoot) || '.'; // absolute paths get converted to relative ones to workspace location if possible
private onInvalidWorkspaceConfigurationFileError(): TPromise<void> {
const message = nls.localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again.");
return this.askToOpenWorkspaceConfigurationFile(message);
}
private onWorkspaceConfigurationFileDirtyError(): TPromise<void> {
const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again.");
return this.askToOpenWorkspaceConfigurationFile(message);
}
private askToOpenWorkspaceConfigurationFile(message: string): TPromise<void> {
return this.choiceService.choose(Severity.Error, message, [nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration File"), nls.localize('close', "Close")], 1)
.then(option => {
switch (option) {
case 0:
this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile');
break;
}
return { path: newWorkspaceRoot };
});
return this.jsonEditingService.write(workspace.configuration, { key: 'folders', value }, true);
} else {
// TODO: Sandeep - Removing all roots?
}
return TPromise.as(null);
}
private validateRoots(roots: URI[]): string[] {
if (!roots) {
return [];
private doEnterWorkspace(mainSidePromise: () => TPromise<IEnterWorkspaceResult>): TPromise<void> {
// Stop the extension host first to give extensions most time to shutdown
this.extensionService.stopExtensionHost();
const startExtensionHost = () => {
this.extensionService.startExtensionHost();
};
return mainSidePromise().then(result => {
// Migrate storage and settings if we are to enter a workspace
if (result) {
return this.migrate(result.workspace).then(() => {
// Show message to user (once) if entering workspace state
if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
this.informUserOnce(); // TODO@Ben remove me after a couple of releases
}
// Reinitialize backup service
const backupFileService = this.backupFileService as BackupFileService; // TODO@Ben ugly cast
backupFileService.initialize(result.backupPath);
// Reinitialize configuration service
const workspaceImpl = this.contextService as WorkspaceService; // TODO@Ben TODO@Sandeep ugly cast
return workspaceImpl.initialize(result.workspace);
});
}
return TPromise.as(void 0);
}).then(startExtensionHost, error => {
startExtensionHost(); // in any case start the extension host again!
return TPromise.wrapError(error);
});
}
private informUserOnce(): void {
if (product.quality !== 'stable') {
return; // only for stable
}
// Prevent duplicates
return distinct(roots.map(root => root.fsPath), root => isLinux ? root : root.toLowerCase());
if (this.storageService.getBoolean(WorkspaceEditingService.INFO_MESSAGE_KEY)) {
return; // user does not want to see it again
}
const closeAction = new Action(
'enterWorkspace.close',
nls.localize('enterWorkspace.close', "Close"),
null,
true,
() => TPromise.as(true)
);
const dontShowAgainAction = new Action(
'enterWorkspace.dontShowAgain',
nls.localize('enterWorkspace.dontShowAgain', "Don't Show Again"),
null,
true,
() => {
this.storageService.store(WorkspaceEditingService.INFO_MESSAGE_KEY, true, StorageScope.GLOBAL);
return TPromise.as(true);
}
);
const moreInfoAction = new Action(
'enterWorkspace.moreInfo',
nls.localize('enterWorkspace.moreInfo', "More Information"),
null,
true,
() => {
const uri = URI.parse('https://go.microsoft.com/fwlink/?linkid=861970');
window.open(uri.toString(true));
return TPromise.as(true);
}
);
this.messageService.show(Severity.Info, {
message: nls.localize('enterWorkspace.prompt', "Learn more about working with multiple folders in VS Code."),
actions: [moreInfoAction, dontShowAgainAction, closeAction]
});
}
private migrate(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
// Storage (UI State) migration
this.migrateStorage(toWorkspace);
// Settings migration (only if we come from a folder workspace)
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
return this.copyWorkspaceSettings(toWorkspace);
}
return TPromise.as(void 0);
}
private migrateStorage(toWorkspace: IWorkspaceIdentifier): void {
// TODO@Ben revisit this when we move away from local storage to a file based approach
const storageImpl = this.storageService as StorageService;
const newWorkspaceId = migrateStorageToMultiRootWorkspace(storageImpl.workspaceId, toWorkspace, storageImpl.workspaceStorage);
storageImpl.setWorkspaceId(newWorkspaceId);
}
public copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
const targetWorkspaceConfiguration = {};
for (const key of this.workspaceConfigurationService.keys().workspace) {
if (configurationProperties[key] && !configurationProperties[key].notMultiRootAdopted && configurationProperties[key].scope === ConfigurationScope.WINDOW) {
targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace;
}
}
return this.jsonEditingService.write(URI.file(toWorkspace.configPath), { key: 'settings', value: targetWorkspaceConfiguration }, true);
}
}

View File

@@ -1,79 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { once } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { StorageService } from 'vs/platform/storage/common/storageService';
import { migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspaceMigrationService } from 'vs/workbench/services/workspace/common/workspaceEditing';
export class WorkspaceMigrationService implements IWorkspaceMigrationService {
public _serviceBrand: any;
private shutdownListener: IDisposable;
constructor(
@IStorageService private storageService: IStorageService,
@IJSONEditingService private jsonEditingService: IJSONEditingService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IConfigurationService private configurationService: IConfigurationService,
@ILifecycleService private lifecycleService: ILifecycleService
) {
}
migrate(toWorkspaceId: IWorkspaceIdentifier): TPromise<void> {
this.migrateStorage(toWorkspaceId);
return this.migrateConfiguration(toWorkspaceId);
}
migrateStorage(toWorkspaceId: IWorkspaceIdentifier): void {
// The shutdown sequence could have been stopped due to a veto. Make sure to
// always dispose the shutdown listener if we are called again in the same session.
if (this.shutdownListener) {
this.shutdownListener.dispose();
this.shutdownListener = void 0;
}
// Since many components write to storage only on shutdown, we register a shutdown listener
// very late to be called as the last one.
this.shutdownListener = once(this.lifecycleService.onShutdown)(() => {
// TODO@Ben revisit this when we move away from local storage to a file based approach
const storageImpl = this.storageService as StorageService;
migrateStorageToMultiRootWorkspace(storageImpl.storageId, toWorkspaceId, storageImpl.workspaceStorage);
});
}
private migrateConfiguration(toWorkspaceId: IWorkspaceIdentifier): TPromise<void> {
if (!this.contextService.hasFolderWorkspace()) {
return TPromise.as(void 0); // return early if not a folder workspace is opened
}
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
const targetWorkspaceConfiguration = {};
for (const key of this.configurationService.keys().workspace) {
if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.WINDOW) {
targetWorkspaceConfiguration[key] = this.configurationService.lookup(key).workspace;
}
}
return this.jsonEditingService.write(URI.file(toWorkspaceId.configPath), { key: 'settings', value: targetWorkspaceConfiguration }, true);
}
}