This commit is contained in:
Sandeep Somavarapu
2017-10-19 22:23:19 +02:00
parent 1eaa61055f
commit 694a599fe0
11 changed files with 445 additions and 394 deletions

View File

@@ -598,4 +598,12 @@ export class SimpleWorkspaceContextService implements IWorkspaceContextService {
public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
return true;
}
public addFolders(foldersToAdd: URI[]): TPromise<void> {
return TPromise.as(void 0);
}
public removeFolders(foldersToRemove: URI[]): TPromise<void> {
return TPromise.as(void 0);
}
}

View File

@@ -13,6 +13,7 @@ import Event from 'vs/base/common/event';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IStoredWorkspaceFolder, isRawFileWorkspaceFolder, isRawUriWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { coalesce, distinct } from 'vs/base/common/arrays';
import { isLinux } from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
export const IWorkspaceContextService = createDecorator<IWorkspaceContextService>('contextService');
@@ -31,21 +32,6 @@ export interface IWorkspaceFoldersChangeEvent {
export interface IWorkspaceContextService {
_serviceBrand: any;
/**
* Provides access to the workspace object the platform is running with. This may be null if the workbench was opened
* without workspace (empty);
*/
getWorkspace(): IWorkspace;
/**
* Return the state of the workbench.
*
* WorkbenchState.EMPTY - if the workbench was opened with empty window or file
* WorkbenchState.FOLDER - if the workbench was opened with a folder
* WorkbenchState.WORKSPACE - if the workbench was opened with a workspace
*/
getWorkbenchState(): WorkbenchState;
/**
* An event which fires on workbench state changes.
*/
@@ -61,6 +47,31 @@ export interface IWorkspaceContextService {
*/
onDidChangeWorkspaceFolders: Event<IWorkspaceFoldersChangeEvent>;
/**
* Provides access to the workspace object the platform is running with. This may be null if the workbench was opened
* without workspace (empty);
*/
getWorkspace(): IWorkspace;
/**
* add folders to the existing workspace
*/
addFolders(folders: URI[]): TPromise<void>;
/**
* remove folders from the existing workspace
*/
removeFolders(folders: URI[]): TPromise<void>;
/**
* Return the state of the workbench.
*
* WorkbenchState.EMPTY - if the workbench was opened with empty window or file
* WorkbenchState.FOLDER - if the workbench was opened with a folder
* WorkbenchState.WORKSPACE - if the workbench was opened with a workspace
*/
getWorkbenchState(): WorkbenchState;
/**
* Returns the folder for the given resource from the workspace.
* Can be null if there is no workspace or the resource is not inside the workspace.

View File

@@ -12,8 +12,8 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IProgress } from 'vs/platform/progress/common/progress';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ISearchResultProvider, ISearchQuery, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchService } from 'vs/platform/search/common/search';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@extHostNamedCustomer(MainContext.MainThreadFileSystem)
export class MainThreadFileSystem implements MainThreadFileSystemShape {
@@ -26,7 +26,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
extHostContext: IExtHostContext,
@IFileService private readonly _fileService: IFileService,
@ISearchService private readonly _searchService: ISearchService,
@IWorkspaceEditingService private readonly _workspaceEditService: IWorkspaceEditingService
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
) {
this._proxy = extHostContext.get(ExtHostContext.ExtHostFileSystem);
}
@@ -45,7 +45,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
}
$onDidAddFileSystemRoot(uri: URI): void {
this._workspaceEditService.addFolders([uri]);
this._workspaceContextService.addFolders([uri]);
}
$onFileSystemChange(handle: number, changes: IFileChange[]): void {

View File

@@ -119,7 +119,7 @@ export class AddRootFolderAction extends BaseWorkspacesAction {
return TPromise.as(null);
}
addFoldersPromise = this.workspaceEditingService.addFolders(folders.map(folder => URI.file(folder)));
addFoldersPromise = this.contextService.addFolders(folders.map(folder => URI.file(folder)));
}
// Empty or Folder
@@ -163,7 +163,7 @@ export class GlobalRemoveRootFolderAction extends BaseWorkspacesAction {
}
// Workspace: remove folder
return this.workspaceEditingService.removeFolders([folder.uri]).then(() => true);
return this.contextService.removeFolders([folder.uri]).then(() => true);
}
return true;
@@ -216,13 +216,13 @@ export class RemoveRootFolderAction extends Action {
private rootUri: URI,
id: string,
label: string,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
@IWorkspaceContextService private contextService: IWorkspaceContextService
) {
super(id, label);
}
public run(): TPromise<any> {
return this.workspaceEditingService.removeFolders([this.rootUri]);
return this.contextService.removeFolders([this.rootUri]);
}
}

View File

@@ -400,7 +400,7 @@ export class ElectronWindow extends Themable {
// Workspace: just add to workspace config
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
this.workspaceEditingService.addFolders(foldersToAdd).done(null, errors.onUnexpectedError);
this.contextService.addFolders(foldersToAdd).done(null, errors.onUnexpectedError);
}
// Single folder or no workspace: create workspace and open

View File

@@ -910,7 +910,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop {
const folders = result.filter(result => result.stat.isDirectory).map(result => result.stat.resource);
if (folders.length > 0) {
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
return this.workspaceEditingService.addFolders(folders);
return this.contextService.addFolders(folders);
}
// If we are in single-folder context, ask for confirmation to create a workspace

View File

@@ -0,0 +1,286 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
import { readFile } from 'vs/base/node/pfs';
import * as errors from 'vs/base/common/errors';
import * as collections from 'vs/base/common/collections';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { ConfigWatcher } from 'vs/base/node/config';
import { CustomConfigurationModel, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels';
import { WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IStoredWorkspace, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import * as extfs from 'vs/base/node/extfs';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
// node.hs helper functions
interface IStat {
resource: URI;
isDirectory?: boolean;
children?: { resource: URI; }[];
}
interface IContent {
resource: URI;
value: string;
}
function resolveContents(resources: URI[]): TPromise<IContent[]> {
const contents: IContent[] = [];
return TPromise.join(resources.map(resource => {
return resolveContent(resource).then(content => {
contents.push(content);
});
})).then(() => contents);
}
function resolveContent(resource: URI): TPromise<IContent> {
return readFile(resource.fsPath).then(contents => ({ resource, value: contents.toString() }));
}
function resolveStat(resource: URI): TPromise<IStat> {
return new TPromise<IStat>((c, e) => {
extfs.readdir(resource.fsPath, (error, children) => {
if (error) {
if ((<any>error).code === 'ENOTDIR') {
c({ resource });
} else {
e(error);
}
} else {
c({
resource,
isDirectory: true,
children: children.map(child => { return { resource: URI.file(paths.join(resource.fsPath, child)) }; })
});
}
});
});
}
export class WorkspaceConfiguration extends Disposable {
private _workspaceConfigPath: URI;
private _workspaceConfigurationWatcher: ConfigWatcher<WorkspaceConfigurationModel>;
private _workspaceConfigurationWatcherDisposables: IDisposable[] = [];
private _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;
load(workspaceConfigPath: URI): TPromise<void> {
if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) {
return this.reload();
}
this._workspaceConfigPath = workspaceConfigPath;
this._workspaceConfigurationWatcherDisposables = dispose(this._workspaceConfigurationWatcherDisposables);
return new TPromise<void>((c, e) => {
this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, {
changeBufferDelay: 300,
onError: error => errors.onUnexpectedError(error),
defaultConfig: new WorkspaceConfigurationModel(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t'), this._workspaceConfigPath.fsPath),
parse: (content: string, parseErrors: any[]) => {
const workspaceConfigurationModel = new WorkspaceConfigurationModel(content, this._workspaceConfigPath.fsPath);
parseErrors = [...workspaceConfigurationModel.errors];
return workspaceConfigurationModel;
}, initCallback: () => c(null)
});
this._workspaceConfigurationWatcherDisposables.push(this._workspaceConfigurationWatcher);
this._workspaceConfigurationWatcher.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire(), this, this._workspaceConfigurationWatcherDisposables);
});
}
private get workspaceConfigurationModel(): WorkspaceConfigurationModel {
return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModel();
}
reload(): TPromise<void> {
return new TPromise<void>(c => this._workspaceConfigurationWatcher.reload(() => c(null)));
}
getFolders(): IStoredWorkspaceFolder[] {
return this.workspaceConfigurationModel.folders;
}
setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): TPromise<void> {
return jsonEditingService.write(this._workspaceConfigPath, { key: 'folders', value: folders }, true)
.then(() => this.reload());
}
getConfiguration(): ConfigurationModel {
return this.workspaceConfigurationModel.workspaceConfiguration;
}
dispose(): void {
dispose(this._workspaceConfigurationWatcherDisposables);
super.dispose();
}
}
export class FolderConfiguration extends Disposable {
private static RELOAD_CONFIGURATION_DELAY = 50;
private bulkFetchFromWorkspacePromise: TPromise;
private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise<ConfigurationModel> };
private reloadConfigurationScheduler: RunOnceScheduler;
private reloadConfigurationEventEmitter: Emitter<FolderConfigurationModel> = new Emitter<FolderConfigurationModel>();
constructor(private folder: URI, private configFolderRelativePath: string, private scope: ConfigurationScope) {
super();
this.workspaceFilePathToConfiguration = Object.create(null);
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY));
}
loadConfiguration(): TPromise<FolderConfigurationModel> {
// Load workspace locals
return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => {
// Consolidate (support *.json files in the workspace settings folder)
const workspaceSettingsConfig = <FolderSettingsModel>workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel(null);
const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => <ScopedConfigurationModel>workspaceConfigFiles[key]);
return new FolderConfigurationModel(workspaceSettingsConfig, otherConfigModels, this.scope);
});
}
private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel }> {
// once: when invoked for the first time we fetch json files that contribute settings
if (!this.bulkFetchFromWorkspacePromise) {
this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => {
if (!stat.isDirectory) {
return TPromise.as([]);
}
return resolveContents(stat.children.filter(stat => {
const isJson = paths.extname(stat.resource.fsPath) === '.json';
if (!isJson) {
return false; // only JSON files
}
return this.isWorkspaceConfigurationFile(this.toFolderRelativePath(stat.resource)); // only workspace config files
}).map(stat => stat.resource));
}, err => [] /* never fail this call */)
.then((contents: IContent[]) => {
contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigModel(content)));
}, errors.onUnexpectedError);
}
// on change: join on *all* configuration file promises so that we can merge them into a single configuration object. this
// happens whenever a config file changes, is deleted, or added
return this.bulkFetchFromWorkspacePromise.then(() => TPromise.join(this.workspaceFilePathToConfiguration));
}
public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<FolderConfigurationModel> {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect workspace configuration files
for (let i = 0, len = events.length; i < len; i++) {
const resource = events[i].resource;
const isJson = paths.extname(resource.fsPath) === '.json';
const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && paths.isEqual(paths.basename(resource.fsPath), this.configFolderRelativePath));
if (!isJson && !isDeletedSettingsFolder) {
continue; // only JSON files or the actual settings folder
}
const workspacePath = this.toFolderRelativePath(resource);
if (!workspacePath) {
continue; // event is not inside workspace
}
// Handle case where ".vscode" got deleted
if (workspacePath === this.configFolderRelativePath && events[i].type === FileChangeType.DELETED) {
this.workspaceFilePathToConfiguration = Object.create(null);
affectedByChanges = true;
}
// only valid workspace config files
if (!this.isWorkspaceConfigurationFile(workspacePath)) {
continue;
}
// insert 'fetch-promises' for add and update events and
// remove promises for delete events
switch (events[i].type) {
case FileChangeType.DELETED:
affectedByChanges = collections.remove(this.workspaceFilePathToConfiguration, workspacePath);
break;
case FileChangeType.UPDATED:
case FileChangeType.ADDED:
this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigModel(content), errors.onUnexpectedError);
affectedByChanges = true;
}
}
if (!affectedByChanges) {
return TPromise.as(null);
}
return new TPromise((c, e) => {
let disposable = this.reloadConfigurationEventEmitter.event(configuration => {
disposable.dispose();
c(configuration);
});
// trigger reload of the configuration if we are affected by changes
if (!this.reloadConfigurationScheduler.isScheduled()) {
this.reloadConfigurationScheduler.schedule();
}
});
}
private createConfigModel(content: IContent): ConfigurationModel {
const path = this.toFolderRelativePath(content.resource);
if (path === WORKSPACE_CONFIG_DEFAULT_PATH) {
return new FolderSettingsModel(content.value, content.resource.toString());
} else {
const matches = /\/([^\.]*)*\.json/.exec(path);
if (matches && matches[1]) {
return new ScopedConfigurationModel(content.value, content.resource.toString(), matches[1]);
}
}
return new CustomConfigurationModel(null);
}
private isWorkspaceConfigurationFile(folderRelativePath: string): boolean {
return [WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY], WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY]].some(p => p === folderRelativePath);
}
private toResource(folderRelativePath: string): URI {
if (typeof folderRelativePath === 'string') {
return URI.file(paths.join(this.folder.fsPath, folderRelativePath));
}
return null;
}
private toFolderRelativePath(resource: URI, toOSPath?: boolean): string {
if (this.contains(resource)) {
return paths.normalize(paths.relative(this.folder.fsPath, resource.fsPath), toOSPath);
}
return null;
}
private contains(resource: URI): boolean {
if (resource) {
return paths.isEqualOrParent(resource.fsPath, this.folder.fsPath, !isLinux /* ignorecase */);
}
return false;
}
}

View File

@@ -7,31 +7,28 @@
import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { TPromise } from 'vs/base/common/winjs.base';
import { dirname } from 'path';
import * as assert from 'vs/base/common/assert';
import Event, { Emitter } from 'vs/base/common/event';
import { StrictResourceMap } from 'vs/base/common/map';
import * as errors from 'vs/base/common/errors';
import { equals } from 'vs/base/common/objects';
import * as collections from 'vs/base/common/collections';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { readFile, stat, writeFile } from 'vs/base/node/pfs';
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue } from 'vs/base/common/async';
import { stat, writeFile } from 'vs/base/node/pfs';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import * as extfs from 'vs/base/node/extfs';
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
import { FileChangesEvent } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { ConfigWatcher } from 'vs/base/node/config';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { CustomConfigurationModel, ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration';
import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel, Configuration, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { FolderConfigurationModel, Configuration, WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { ConfigurationService as GlobalConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationNode, IConfigurationRegistry, Extensions, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry';
import { createHash } from 'crypto';
import { getWorkspaceLabel, IWorkspacesService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { getWorkspaceLabel, IWorkspacesService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
@@ -39,17 +36,10 @@ import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
interface IStat {
resource: URI;
isDirectory?: boolean;
children?: { resource: URI; }[];
}
interface IContent {
resource: URI;
value: string;
}
import { WorkspaceConfiguration, FolderConfiguration } from 'vs/workbench/services/configuration/node/configuration';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
import { Schemas } from 'vs/base/common/network';
import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces';
export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService {
@@ -61,6 +51,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
private workspaceConfiguration: WorkspaceConfiguration;
private cachedFolderConfigs: StrictResourceMap<FolderConfiguration>;
private workspaceEditingQueue: Queue<void>;
protected readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
public readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
@@ -74,6 +66,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
public readonly onDidChangeWorkbenchState: Event<WorkbenchState> = this._onDidChangeWorkbenchState.event;
private configurationEditingService: ConfigurationEditingService;
private jsonEditingService: JSONEditingService;
constructor(private environmentService: IEnvironmentService, private workspacesService: IWorkspacesService, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) {
super();
@@ -84,6 +77,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
this.baseConfigurationService = this._register(new GlobalConfigurationService(environmentService));
this._register(this.baseConfigurationService.onDidChangeConfiguration(e => this.onBaseConfigurationChanged(e)));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(e => this.registerConfigurationSchemas()));
this.workspaceEditingQueue = new Queue<void>();
}
// Workspace Context Service Impl
@@ -111,6 +106,16 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
return this.workspace.getFolder(resource);
}
public addFolders(foldersToAdd: URI[]): TPromise<void> {
assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
return this.workspaceEditingQueue.queue(() => this.doAddFolders(foldersToAdd));
}
public removeFolders(foldersToRemove: URI[]): TPromise<void> {
assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
return this.workspaceEditingQueue.queue(() => this.doRemoveFolders(foldersToRemove));
}
public isInsideWorkspace(resource: URI): boolean {
return !!this.getWorkspaceFolder(resource);
}
@@ -125,6 +130,79 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
return false;
}
private doAddFolders(foldersToAdd: URI[]): TPromise<void> {
if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
return TPromise.as(void 0); // we need a workspace to begin with
}
const currentWorkspaceFolders = this.getWorkspace().folders;
const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);
const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];
const workspaceConfigFolder = dirname(this.getWorkspace().configuration.fsPath);
foldersToAdd.forEach(folderToAdd => {
if (this.contains(currentWorkspaceFolderUris, folderToAdd)) {
return; // already existing
}
// File resource: use "path" property
if (folderToAdd.scheme === Schemas.file) {
storedFoldersToAdd.push({
path: massageFolderPathForWorkspace(folderToAdd.fsPath, workspaceConfigFolder, currentStoredFolders)
});
}
// Any other resource: use "uri" property
else {
storedFoldersToAdd.push({
uri: folderToAdd.toString(true)
});
}
});
if (storedFoldersToAdd.length > 0) {
return this.workspaceConfiguration.setFolders([...currentStoredFolders, ...storedFoldersToAdd], this.jsonEditingService);
}
return TPromise.as(void 0);
}
private doRemoveFolders(foldersToRemove: URI[]): TPromise<void> {
if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
return TPromise.as(void 0); // we need a workspace to begin with
}
const currentWorkspaceFolders = this.getWorkspace().folders;
const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);
const newStoredFolders: IStoredWorkspaceFolder[] = currentStoredFolders.filter((folder, index) => {
if (!isStoredWorkspaceFolder(folder)) {
return true; // keep entries which are unrelated
}
return !this.contains(foldersToRemove, currentWorkspaceFolders[index].uri); // keep entries which are unrelated
});
if (newStoredFolders.length !== currentStoredFolders.length) {
return this.workspaceConfiguration.setFolders(newStoredFolders, this.jsonEditingService);
}
return TPromise.as(void 0);
}
private contains(resources: URI[], toCheck: URI): boolean {
return resources.some(resource => {
if (isLinux) {
return resource.toString() === toCheck.toString();
}
return resource.toString().toLowerCase() === toCheck.toString().toLowerCase();
});
}
// Workspace Configuration Service Impl
getConfigurationData(): IConfigurationData {
@@ -198,6 +276,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
setInstantiationService(instantiationService: IInstantiationService): void {
this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
}
handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<void> {
@@ -226,8 +305,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
return this.workspaceConfiguration.load(workspaceConfigPath)
.then(() => {
const workspaceConfigurationModel = this.workspaceConfiguration.workspaceConfigurationModel;
const workspaceFolders = toWorkspaceFolders(workspaceConfigurationModel.folders, URI.file(paths.dirname(workspaceConfigPath.fsPath)));
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(paths.dirname(workspaceConfigPath.fsPath)));
const workspaceId = workspaceIdentifier.id;
const workspaceName = getWorkspaceLabel({ id: workspaceId, configPath: workspaceConfigPath.fsPath }, this.environmentService);
return new Workspace(workspaceId, workspaceName, workspaceFolders, workspaceConfigPath);
@@ -341,7 +419,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
case WorkbenchState.FOLDER:
return folderConfigurations[0];
case WorkbenchState.WORKSPACE:
return this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration;
return this.workspaceConfiguration.getConfiguration();
default:
return new ConfigurationModel();
}
@@ -378,8 +456,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
private onWorkspaceConfigurationChanged(): TPromise<void> {
if (this.workspace && this.workspace.configuration && this._configuration) {
const workspaceConfigurationChangeEvent = this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration);
let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.workspaceConfigurationModel.folders, URI.file(paths.dirname(this.workspace.configuration.fsPath)));
const workspaceConfigurationChangeEvent = this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(paths.dirname(this.workspace.configuration.fsPath)));
const changes = this.compareFolders(this.workspace.folders, configuredFolders);
if (changes.added.length || changes.removed.length || changes.changed.length) {
this.workspace.folders = configuredFolders;
@@ -557,244 +635,6 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
}
}
class WorkspaceConfiguration extends Disposable {
private _workspaceConfigPath: URI;
private _workspaceConfigurationWatcher: ConfigWatcher<WorkspaceConfigurationModel>;
private _workspaceConfigurationWatcherDisposables: IDisposable[] = [];
private _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;
load(workspaceConfigPath: URI): TPromise<void> {
if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) {
return this.reload();
}
this._workspaceConfigPath = workspaceConfigPath;
this._workspaceConfigurationWatcherDisposables = dispose(this._workspaceConfigurationWatcherDisposables);
return new TPromise<void>((c, e) => {
this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, {
changeBufferDelay: 300,
onError: error => errors.onUnexpectedError(error),
defaultConfig: new WorkspaceConfigurationModel(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t'), this._workspaceConfigPath.fsPath),
parse: (content: string, parseErrors: any[]) => {
const workspaceConfigurationModel = new WorkspaceConfigurationModel(content, this._workspaceConfigPath.fsPath);
parseErrors = [...workspaceConfigurationModel.errors];
return workspaceConfigurationModel;
}, initCallback: () => c(null)
});
this._workspaceConfigurationWatcherDisposables.push(this._workspaceConfigurationWatcher);
this._workspaceConfigurationWatcher.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire(), this, this._workspaceConfigurationWatcherDisposables);
});
}
get workspaceConfigurationModel(): WorkspaceConfigurationModel {
return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModel();
}
reload(): TPromise<void> {
return new TPromise<void>(c => this._workspaceConfigurationWatcher.reload(() => c(null)));
}
dispose(): void {
dispose(this._workspaceConfigurationWatcherDisposables);
super.dispose();
}
}
class FolderConfiguration extends Disposable {
private static RELOAD_CONFIGURATION_DELAY = 50;
private bulkFetchFromWorkspacePromise: TPromise;
private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise<ConfigurationModel> };
private reloadConfigurationScheduler: RunOnceScheduler;
private reloadConfigurationEventEmitter: Emitter<FolderConfigurationModel> = new Emitter<FolderConfigurationModel>();
constructor(private folder: URI, private configFolderRelativePath: string, private scope: ConfigurationScope) {
super();
this.workspaceFilePathToConfiguration = Object.create(null);
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY));
}
loadConfiguration(): TPromise<FolderConfigurationModel> {
// Load workspace locals
return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => {
// Consolidate (support *.json files in the workspace settings folder)
const workspaceSettingsConfig = <FolderSettingsModel>workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel(null);
const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => <ScopedConfigurationModel>workspaceConfigFiles[key]);
return new FolderConfigurationModel(workspaceSettingsConfig, otherConfigModels, this.scope);
});
}
private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel }> {
// once: when invoked for the first time we fetch json files that contribute settings
if (!this.bulkFetchFromWorkspacePromise) {
this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => {
if (!stat.isDirectory) {
return TPromise.as([]);
}
return resolveContents(stat.children.filter(stat => {
const isJson = paths.extname(stat.resource.fsPath) === '.json';
if (!isJson) {
return false; // only JSON files
}
return this.isWorkspaceConfigurationFile(this.toFolderRelativePath(stat.resource)); // only workspace config files
}).map(stat => stat.resource));
}, err => [] /* never fail this call */)
.then((contents: IContent[]) => {
contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigModel(content)));
}, errors.onUnexpectedError);
}
// on change: join on *all* configuration file promises so that we can merge them into a single configuration object. this
// happens whenever a config file changes, is deleted, or added
return this.bulkFetchFromWorkspacePromise.then(() => TPromise.join(this.workspaceFilePathToConfiguration));
}
public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<FolderConfigurationModel> {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect workspace configuration files
for (let i = 0, len = events.length; i < len; i++) {
const resource = events[i].resource;
const isJson = paths.extname(resource.fsPath) === '.json';
const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && paths.isEqual(paths.basename(resource.fsPath), this.configFolderRelativePath));
if (!isJson && !isDeletedSettingsFolder) {
continue; // only JSON files or the actual settings folder
}
const workspacePath = this.toFolderRelativePath(resource);
if (!workspacePath) {
continue; // event is not inside workspace
}
// Handle case where ".vscode" got deleted
if (workspacePath === this.configFolderRelativePath && events[i].type === FileChangeType.DELETED) {
this.workspaceFilePathToConfiguration = Object.create(null);
affectedByChanges = true;
}
// only valid workspace config files
if (!this.isWorkspaceConfigurationFile(workspacePath)) {
continue;
}
// insert 'fetch-promises' for add and update events and
// remove promises for delete events
switch (events[i].type) {
case FileChangeType.DELETED:
affectedByChanges = collections.remove(this.workspaceFilePathToConfiguration, workspacePath);
break;
case FileChangeType.UPDATED:
case FileChangeType.ADDED:
this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigModel(content), errors.onUnexpectedError);
affectedByChanges = true;
}
}
if (!affectedByChanges) {
return TPromise.as(null);
}
return new TPromise((c, e) => {
let disposable = this.reloadConfigurationEventEmitter.event(configuration => {
disposable.dispose();
c(configuration);
});
// trigger reload of the configuration if we are affected by changes
if (!this.reloadConfigurationScheduler.isScheduled()) {
this.reloadConfigurationScheduler.schedule();
}
});
}
private createConfigModel(content: IContent): ConfigurationModel {
const path = this.toFolderRelativePath(content.resource);
if (path === WORKSPACE_CONFIG_DEFAULT_PATH) {
return new FolderSettingsModel(content.value, content.resource.toString());
} else {
const matches = /\/([^\.]*)*\.json/.exec(path);
if (matches && matches[1]) {
return new ScopedConfigurationModel(content.value, content.resource.toString(), matches[1]);
}
}
return new CustomConfigurationModel(null);
}
private isWorkspaceConfigurationFile(folderRelativePath: string): boolean {
return [WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY], WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY]].some(p => p === folderRelativePath);
}
private toResource(folderRelativePath: string): URI {
if (typeof folderRelativePath === 'string') {
return URI.file(paths.join(this.folder.fsPath, folderRelativePath));
}
return null;
}
private toFolderRelativePath(resource: URI, toOSPath?: boolean): string {
if (this.contains(resource)) {
return paths.normalize(paths.relative(this.folder.fsPath, resource.fsPath), toOSPath);
}
return null;
}
private contains(resource: URI): boolean {
if (resource) {
return paths.isEqualOrParent(resource.fsPath, this.folder.fsPath, !isLinux /* ignorecase */);
}
return false;
}
}
// node.hs helper functions
function resolveContents(resources: URI[]): TPromise<IContent[]> {
const contents: IContent[] = [];
return TPromise.join(resources.map(resource => {
return resolveContent(resource).then(content => {
contents.push(content);
});
})).then(() => contents);
}
function resolveContent(resource: URI): TPromise<IContent> {
return readFile(resource.fsPath).then(contents => ({ resource, value: contents.toString() }));
}
function resolveStat(resource: URI): TPromise<IStat> {
return new TPromise<IStat>((c, e) => {
extfs.readdir(resource.fsPath, (error, children) => {
if (error) {
if ((<any>error).code === 'ENOTDIR') {
c({ resource });
} else {
e(error);
}
} else {
c({
resource,
isDirectory: true,
children: children.map(child => { return { resource: URI.file(paths.join(resource.fsPath, child)) }; })
});
}
});
});
}
interface IExportedConfigurationNode {
name: string;
description: string;

View File

@@ -5,7 +5,6 @@
'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';
@@ -15,16 +14,6 @@ export interface IWorkspaceEditingService {
_serviceBrand: ServiceIdentifier<any>;
/**
* add folders to the existing workspace
*/
addFolders(folders: URI[]): TPromise<void>;
/**
* remove folders from the existing workspace
*/
removeFolders(folders: URI[]): 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.

View File

@@ -9,14 +9,10 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWindowsService, IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspacesService, IStoredWorkspaceFolder, IWorkspaceIdentifier, isStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { dirname } from 'path';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces';
import { isLinux } from 'vs/base/common/platform';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration';
import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -26,7 +22,6 @@ 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 { Schemas } from 'vs/base/common/network';
export class WorkspaceEditingService implements IWorkspaceEditingService {
@@ -35,10 +30,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
constructor(
@IJSONEditingService private jsonEditingService: IJSONEditingService,
@IWorkspaceContextService private contextService: WorkspaceService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWindowsService private windowsService: IWindowsService,
@IWindowService private windowService: IWindowService,
@IWorkspacesService private workspacesService: IWorkspacesService,
@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
@IStorageService private storageService: IStorageService,
@IExtensionService private extensionService: IExtensionService,
@@ -46,89 +38,6 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
) {
}
public addFolders(foldersToAdd: URI[]): TPromise<void> {
if (!this.isSupported()) {
return TPromise.as(void 0); // we need a workspace to begin with
}
const currentWorkspaceFolders = this.contextService.getWorkspace().folders;
const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);
const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];
const workspaceConfigFolder = dirname(this.contextService.getWorkspace().configuration.fsPath);
foldersToAdd.forEach(folderToAdd => {
if (this.contains(currentWorkspaceFolderUris, folderToAdd)) {
return; // already existing
}
// File resource: use "path" property
if (folderToAdd.scheme === Schemas.file) {
storedFoldersToAdd.push({
path: massageFolderPathForWorkspace(folderToAdd.fsPath, workspaceConfigFolder, currentStoredFolders)
});
}
// Any other resource: use "uri" property
else {
storedFoldersToAdd.push({
uri: folderToAdd.toString(true)
});
}
});
if (storedFoldersToAdd.length > 0) {
return this.doSetFolders([...currentStoredFolders, ...storedFoldersToAdd]);
}
return TPromise.as(void 0);
}
public removeFolders(foldersToRemove: URI[]): TPromise<void> {
if (!this.isSupported()) {
return TPromise.as(void 0); // we need a workspace to begin with
}
const currentWorkspaceFolders = this.contextService.getWorkspace().folders;
const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);
const newStoredFolders: IStoredWorkspaceFolder[] = currentStoredFolders.filter((folder, index) => {
if (!isStoredWorkspaceFolder(folder)) {
return true; // keep entries which are unrelated
}
return !this.contains(foldersToRemove, currentWorkspaceFolders[index].uri); // keep entries which are unrelated
});
if (newStoredFolders.length !== currentStoredFolders.length) {
return this.doSetFolders(newStoredFolders);
}
return TPromise.as(void 0);
}
private doSetFolders(folders: IStoredWorkspaceFolder[]): TPromise<void> {
const workspace = this.contextService.getWorkspace();
return this.jsonEditingService.write(workspace.configuration, { key: 'folders', value: folders }, true);
}
private isSupported(): boolean {
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; // we need a multi folder workspace to begin with;
}
private contains(resources: URI[], toCheck: URI): boolean {
return resources.some(resource => {
if (isLinux) {
return resource.toString() === toCheck.toString();
}
return resource.toString().toLowerCase() === toCheck.toString().toLowerCase();
});
}
public createAndEnterWorkspace(folderPaths?: string[], path?: string): TPromise<void> {
return this.doEnterWorkspace(() => this.windowService.createAndEnterWorkspace(folderPaths, path));
}

View File

@@ -118,6 +118,14 @@ export class TestContextService implements IWorkspaceContextService {
return this.workspace;
}
public addFolders(foldersToAdd: URI[]): TPromise<void> {
return TPromise.as(void 0);
}
public removeFolders(foldersToRemove: URI[]): TPromise<void> {
return TPromise.as(void 0);
}
public getWorkspaceFolder(resource: URI): IWorkspaceFolder {
return this.isInsideWorkspace(resource) ? this.workspace.folders[0] : null;
}