mirror of
https://github.com/microsoft/vscode.git
synced 2026-03-05 08:17:17 +00:00
Fix #33323
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
286
src/vs/workbench/services/configuration/node/configuration.ts
Normal file
286
src/vs/workbench/services/configuration/node/configuration.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user