mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
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:
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user