提交 f963973e 编写于 作者: B Benjamin Pasero

first cut of proposed updateWorkspaceFolders API

上级 752c7b78
...@@ -157,6 +157,37 @@ declare module 'vscode' { ...@@ -157,6 +157,37 @@ declare module 'vscode' {
export namespace workspace { export namespace workspace {
export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider): Disposable; export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider): Disposable;
/**
* Updates the workspace folders of the currently opened workspace. This method allows to add and remove
* workspace folders a the same time.
*
* Example: adding a new workspace folder at the end of workspace folders
* ```typescript
* workspace.updateWorkspaceFolders(workspace.workspaceFolders ? workspace.workspaceFolders.length : 0, null, [{ uri: ...}])
* ```
*
* Example: removing the first workspace folder
* ```typescript
* workspace.updateWorkspaceFolders(0, 1)
* ```
*
* Example: replacing an existing workspace folder with a new one
* ```typescript
* workspace.updateWorkspaceFolders(0, 1, [{ uri: ...}])
* ```
*
* Note: if the first workspace folder is removed or changed, all extensions will be restarted
* so that the (deprecated) `rootPath` property is updated to point to the first workspace
* folder.
*
* @param index the zero-based index in the list of currently opened [workspace folders](#WorkspaceFolder)
* from where to delete workspace folders or from where to add to.
* @param deleteCount the optional number of workspace folders to delete from the index that is provided.
* @param workspaceFoldersToAdd the optional number of workspace folders to add
* @return A thenable that resolves when the workspace folder was removed successfully
*/
export function updateWorkspaceFolders(index: number, deleteCount?: number, workspaceFoldersToAdd?: { uri: Uri, name?: string }[]): Thenable<boolean>;
} }
export namespace window { export namespace window {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
'use strict'; 'use strict';
import { isPromiseCanceledError } from 'vs/base/common/errors'; import { isPromiseCanceledError } from 'vs/base/common/errors';
import URI from 'vs/base/common/uri'; import URI, { UriComponents } from 'vs/base/common/uri';
import { ISearchService, QueryType, ISearchQuery, IFolderQuery, ISearchConfiguration } from 'vs/platform/search/common/search'; import { ISearchService, QueryType, ISearchQuery, IFolderQuery, ISearchConfiguration } from 'vs/platform/search/common/search';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
...@@ -13,11 +13,20 @@ import { TPromise } from 'vs/base/common/winjs.base'; ...@@ -13,11 +13,20 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { IMessageService, IConfirmation } from 'vs/platform/message/common/message';
import { localize } from 'vs/nls';
import { getPathLabel } from 'vs/base/common/labels';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
@extHostNamedCustomer(MainContext.MainThreadWorkspace) @extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape { export class MainThreadWorkspace implements MainThreadWorkspaceShape {
private static CONFIRM_CHANGES_TO_WORKSPACES_KEY = 'workbench.confirmChangesToWorkspaceFromExtensions';
private readonly _toDispose: IDisposable[] = []; private readonly _toDispose: IDisposable[] = [];
private readonly _activeSearches: { [id: number]: TPromise<URI[]> } = Object.create(null); private readonly _activeSearches: { [id: number]: TPromise<URI[]> } = Object.create(null);
private readonly _proxy: ExtHostWorkspaceShape; private readonly _proxy: ExtHostWorkspaceShape;
...@@ -27,7 +36,10 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -27,7 +36,10 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@ISearchService private readonly _searchService: ISearchService, @ISearchService private readonly _searchService: ISearchService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@ITextFileService private readonly _textFileService: ITextFileService, @ITextFileService private readonly _textFileService: ITextFileService,
@IConfigurationService private _configurationService: IConfigurationService @IConfigurationService private _configurationService: IConfigurationService,
@IWorkspaceEditingService private _workspaceEditingService: IWorkspaceEditingService,
@IMessageService private _messageService: IMessageService,
@IEnvironmentService private _environmentService: IEnvironmentService
) { ) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose); this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
...@@ -45,6 +57,110 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -45,6 +57,110 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
// --- workspace --- // --- workspace ---
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount?: number, add?: { uri: UriComponents, name?: string }[]): Thenable<boolean> {
let workspaceFoldersToAdd: { uri: URI, name?: string }[] = [];
if (Array.isArray(add)) {
workspaceFoldersToAdd = add.map(f => ({ uri: URI.revive(f.uri), name: f.name }));
}
let workspaceFoldersToRemove: URI[] = [];
if (typeof deleteCount === 'number') {
workspaceFoldersToRemove = this._contextService.getWorkspace().folders.slice(index, index + deleteCount).map(f => f.uri);
}
if (!workspaceFoldersToAdd.length && !workspaceFoldersToRemove.length) {
return TPromise.as(false); // return early if we neither have folders to add nor remove
}
return this.confirmUpdateWorkspaceFolders(extensionName, workspaceFoldersToRemove, workspaceFoldersToAdd.map(f => f.uri)).then(confirmed => {
if (!confirmed) {
return TPromise.as(false); // return if not confirmed by the user
}
return this._workspaceEditingService.updateFolders(index, deleteCount, workspaceFoldersToAdd, true).then(() => true);
});
}
private confirmUpdateWorkspaceFolders(extensionName: string, workspaceFoldersToRemove?: URI[], workspaceFoldersToAdd?: URI[]): Thenable<boolean> {
if (!this._configurationService.getValue<boolean>(MainThreadWorkspace.CONFIRM_CHANGES_TO_WORKSPACES_KEY)) {
return TPromise.as(true); // return confirmed if the setting indicates this
}
return this._messageService.confirmWithCheckbox(this.getConfirmationOptions(extensionName, workspaceFoldersToRemove, workspaceFoldersToAdd)).then(confirmation => {
let updateConfirmSettingsPromise: TPromise<void> = TPromise.as(void 0);
if (confirmation.confirmed && confirmation.checkboxChecked === true) {
updateConfirmSettingsPromise = this._configurationService.updateValue(MainThreadWorkspace.CONFIRM_CHANGES_TO_WORKSPACES_KEY, false, ConfigurationTarget.USER);
}
return updateConfirmSettingsPromise.then(() => confirmation.confirmed);
});
}
private getConfirmationOptions(extensionName, workspaceFoldersToRemove?: URI[], workspaceFoldersToAdd?: URI[]): IConfirmation {
const wantsToDelete = Array.isArray(workspaceFoldersToRemove) && workspaceFoldersToRemove.length;
const wantsToAdd = Array.isArray(workspaceFoldersToAdd) && workspaceFoldersToAdd.length;
let message: string;
let detail: string;
let primaryButton: string;
// Add Folders
if (wantsToAdd && !wantsToDelete) {
if (workspaceFoldersToAdd.length === 1) {
message = localize('folderMessageAddSingleFolder', "Extension '{0}' wants to add a folder to the workspace. Please confirm.", extensionName);
primaryButton = localize('addFolder', "&&Add Folder");
} else {
message = localize('folderMessageAddMultipleFolders', "Extension '{0}' wants to add {1} folders to the workspace. Please confirm.", extensionName, workspaceFoldersToAdd.length);
primaryButton = localize('addFolders', "&&Add Folders");
}
detail = this.getConfirmationDetail(workspaceFoldersToAdd, false);
}
// Delete Folders
else if (wantsToDelete && !wantsToAdd) {
if (workspaceFoldersToRemove.length === 1) {
message = localize('folderMessageRemoveSingleFolder', "Extension '{0}' wants to remove a folder from the workspace. Please confirm.", extensionName);
primaryButton = localize('removeFolder', "&&Remove Folder");
} else {
message = localize('folderMessageRemoveMultipleFolders', "Extension '{0}' wants to remove folders from the workspace. Please confirm.", extensionName);
primaryButton = localize('removeFolders', "&&Remove Folders");
}
detail = this.getConfirmationDetail(workspaceFoldersToRemove, true);
}
// Change Folders
else {
message = localize('folderChangeFolder', "Extension '{0}' wants to change the folders of the workspace. Please confirm.", extensionName);
primaryButton = localize('changeFolders', "&&Change Folders");
detail = [this.getConfirmationDetail(workspaceFoldersToAdd, false), this.getConfirmationDetail(workspaceFoldersToRemove, true)].join('\n\n');
}
return { message, detail, type: 'question', primaryButton, checkbox: { label: localize('doNotAskAgain', "Do not ask me again") } };
}
private getConfirmationDetail(folders: URI[], isRemove: boolean): string {
const getFolderName = uri => {
return uri.scheme === 'file' ? getPathLabel(uri.fsPath, null, this._environmentService) : uri.toString();
};
if (folders.length === 1) {
if (isRemove) {
return [localize('folderToRemove', "Folder to remove:"), ...folders.map(f => getFolderName(f))].join('\n');
}
return [localize('folderToAdd', "Folder to add:"), ...folders.map(f => getFolderName(f))].join('\n');
}
if (isRemove) {
return [localize('foldersToRemove', "Folders to remove:"), ...folders.map(f => getFolderName(f))].join('\n');
}
return [localize('foldersToAdd', "Folders to add:"), ...folders.map(f => getFolderName(f))].join('\n');
}
private _onDidChangeWorkspace(): void { private _onDidChangeWorkspace(): void {
this._proxy.$acceptWorkspaceData(this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace()); this._proxy.$acceptWorkspaceData(this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace());
} }
...@@ -123,3 +239,19 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -123,3 +239,19 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
}); });
} }
} }
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'workbench',
'order': 7,
'title': localize('workbenchConfigurationTitle', "Workbench"),
'type': 'object',
'properties': {
'workbench.confirmChangesToWorkspaceFromExtensions': {
'type': 'boolean',
'description': localize('confirmChangesFromExtensions', "Controls if a confirmation should be shown for extensions that add or remove workspace folders."),
'default': true
}
}
});
\ No newline at end of file
...@@ -418,6 +418,9 @@ export function createApiFactory( ...@@ -418,6 +418,9 @@ export function createApiFactory(
set name(value) { set name(value) {
throw errors.readonly(); throw errors.readonly();
}, },
updateWorkspaceFolders: proposedApiFunction(extension, (index, deleteCount, workspaceFoldersToAdd) => {
return extHostWorkspace.updateWorkspaceFolders(extension.displayName || extension.name, index, deleteCount, workspaceFoldersToAdd);
}),
onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) { onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) {
return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables); return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables);
}, },
......
...@@ -364,6 +364,7 @@ export interface MainThreadWorkspaceShape extends IDisposable { ...@@ -364,6 +364,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
$startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable<UriComponents[]>; $startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable<UriComponents[]>;
$cancelSearch(requestId: number): Thenable<boolean>; $cancelSearch(requestId: number): Thenable<boolean>;
$saveAll(includeUntitled?: boolean): Thenable<boolean>; $saveAll(includeUntitled?: boolean): Thenable<boolean>;
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount?: number, workspaceFoldersToAdd?: { uri: UriComponents, name?: string }[]): Thenable<boolean>;
} }
export interface IFileChangeDto { export interface IFileChangeDto {
......
...@@ -86,6 +86,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { ...@@ -86,6 +86,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
} }
} }
updateWorkspaceFolders(extensionName: string, index: number, deleteCount?: number, workspaceFoldersToAdd?: { uri: vscode.Uri, name?: string }[]): Thenable<boolean> {
return this._proxy.$updateWorkspaceFolders(extensionName, index, deleteCount, workspaceFoldersToAdd);
}
getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder { getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder {
if (!this._workspace) { if (!this._workspace) {
return undefined; return undefined;
......
...@@ -178,7 +178,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat ...@@ -178,7 +178,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
if (storedFoldersToAdd.length > 0) { if (storedFoldersToAdd.length > 0) {
let newStoredWorkspaceFolders: IStoredWorkspaceFolder[] = []; let newStoredWorkspaceFolders: IStoredWorkspaceFolder[] = [];
if (typeof index === 'number' && index >= 0 && index < currentStoredFolders.length - 1) { if (typeof index === 'number' && index >= 0 && index < currentStoredFolders.length) {
newStoredWorkspaceFolders = currentStoredFolders.slice(0); newStoredWorkspaceFolders = currentStoredFolders.slice(0);
newStoredWorkspaceFolders.splice(index, 0, ...storedFoldersToAdd); newStoredWorkspaceFolders.splice(index, 0, ...storedFoldersToAdd);
} else { } else {
......
...@@ -49,16 +49,18 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { ...@@ -49,16 +49,18 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
public updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void> { public updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void> {
const folders = this.contextService.getWorkspace().folders; const folders = this.contextService.getWorkspace().folders;
if (index < 0 || index > folders.length - 1) {
return TPromise.wrapError(new Error(nls.localize('errorInvalidIndex', "The index for updating workspace folders is invalid.")));
}
if (typeof deleteCount === 'number' && (deleteCount < 0 || index + deleteCount > folders.length - 1)) { let foldersToDelete: URI[] = [];
return TPromise.wrapError(new Error(nls.localize('errorInvalidDelete', "The number of workspace folders to delete is invalid."))); if (typeof deleteCount === 'number') {
foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri);
} }
const wantsToDelete = typeof deleteCount === 'number'; const wantsToDelete = foldersToDelete.length > 0;
const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length; const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length > 0;
if (!wantsToAdd && !wantsToDelete) {
return TPromise.as(void 0); // return early if there is nothing to do
}
// Add Folders // Add Folders
if (wantsToAdd && !wantsToDelete) { if (wantsToAdd && !wantsToDelete) {
...@@ -67,11 +69,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { ...@@ -67,11 +69,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
// Delete Folders // Delete Folders
if (wantsToDelete && !wantsToAdd) { if (wantsToDelete && !wantsToAdd) {
return this.removeFolders(folders.slice(index, index + deleteCount).map(f => f.uri)); return this.removeFolders(foldersToDelete);
} }
// Add & Delete Folders (first remove and then add to allow for updating existing folders) // Add & Delete Folders (first remove and then add to allow for updating existing folders)
return this.removeFolders(folders.slice(index, index + deleteCount).map(f => f.uri)).then(() => { return this.removeFolders(foldersToDelete).then(() => {
return this.doAddFolders(foldersToAdd, index, donotNotifyError); return this.doAddFolders(foldersToAdd, index, donotNotifyError);
}); });
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册