mirror of
https://github.com/Microsoft/vscode
synced 2024-10-03 01:46:42 +00:00
Merge pull request #42026 from Microsoft/ben/workspace-api
Add API to update workspace folders
This commit is contained in:
commit
59ec992fab
|
@ -205,8 +205,8 @@ export class Workspace implements IWorkspace {
|
|||
export class WorkspaceFolder implements IWorkspaceFolder {
|
||||
|
||||
readonly uri: URI;
|
||||
readonly name: string;
|
||||
readonly index: number;
|
||||
name: string;
|
||||
index: number;
|
||||
|
||||
constructor(data: IWorkspaceFolderData,
|
||||
readonly raw?: IStoredWorkspaceFolder) {
|
||||
|
|
35
src/vs/vscode.proposed.d.ts
vendored
35
src/vs/vscode.proposed.d.ts
vendored
|
@ -157,6 +157,41 @@ declare module 'vscode' {
|
|||
|
||||
export namespace workspace {
|
||||
export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider): Disposable;
|
||||
|
||||
/**
|
||||
* Updates the workspace folders of the currently opened workspace. This method allows to add, remove
|
||||
* and change 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: ...});
|
||||
* ```
|
||||
*
|
||||
* It is valid to remove an existing workspace folder and add it again with a different name
|
||||
* to rename that folder.
|
||||
*
|
||||
* Note: if the first workspace folder is added, removed or changed, all extensions will be restarted
|
||||
* so that the (deprecated) `rootPath` property is updated to point to the first workspace
|
||||
* folder.
|
||||
* @param start the zero-based location in the list of currently opened [workspace folders](#WorkspaceFolder)
|
||||
* from which to start deleting workspace folders.
|
||||
* @param deleteCount the optional number of workspace folders to remove.
|
||||
* @param workspaceFoldersToAdd the optional variable set of workspace folders to add in place of the deleted ones.
|
||||
* Each workspace is identified with a mandatory URI and an optional name.
|
||||
* @return true if the operation was successfully started. Use the [onDidChangeWorkspaceFolders()](#onDidChangeWorkspaceFolders)
|
||||
* event to get notified when the workspace folders have been updated.
|
||||
*/
|
||||
export function updateWorkspaceFolders(start: number, deleteCount: number, ...workspaceFoldersToAdd: { uri: Uri, name?: string }[]): boolean;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
'use strict';
|
||||
|
||||
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 { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
@ -14,6 +14,9 @@ import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainCo
|
|||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
|
||||
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
|
@ -27,7 +30,9 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
|||
@ISearchService private readonly _searchService: ISearchService,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IWorkspaceEditingService private _workspaceEditingService: IWorkspaceEditingService,
|
||||
@IStatusbarService private _statusbarService: IStatusbarService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
|
||||
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
|
||||
|
@ -45,6 +50,47 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
|||
|
||||
// --- workspace ---
|
||||
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, foldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void> {
|
||||
const workspaceFoldersToAdd = foldersToAdd.map(f => ({ uri: URI.revive(f.uri), name: f.name }));
|
||||
|
||||
// Indicate in status message
|
||||
this._statusbarService.setStatusMessage(this.getStatusMessage(extensionName, workspaceFoldersToAdd.length, deleteCount), 10 * 1000 /* 10s */);
|
||||
|
||||
return this._workspaceEditingService.updateFolders(index, deleteCount, workspaceFoldersToAdd, true);
|
||||
}
|
||||
|
||||
private getStatusMessage(extensionName, addCount: number, removeCount: number): string {
|
||||
let message: string;
|
||||
|
||||
const wantsToAdd = addCount > 0;
|
||||
const wantsToDelete = removeCount > 0;
|
||||
|
||||
// Add Folders
|
||||
if (wantsToAdd && !wantsToDelete) {
|
||||
if (addCount === 1) {
|
||||
message = localize('folderStatusMessageAddSingleFolder', "Extension '{0}' added 1 folder to the workspace", extensionName);
|
||||
} else {
|
||||
message = localize('folderStatusMessageAddMultipleFolders', "Extension '{0}' added {1} folders to the workspace", extensionName, addCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Folders
|
||||
else if (wantsToDelete && !wantsToAdd) {
|
||||
if (removeCount === 1) {
|
||||
message = localize('folderStatusMessageRemoveSingleFolder', "Extension '{0}' removed 1 folder from the workspace", extensionName);
|
||||
} else {
|
||||
message = localize('folderStatusMessageRemoveMultipleFolders', "Extension '{0}' removed {1} folders from the workspace", extensionName, removeCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Change Folders
|
||||
else {
|
||||
message = localize('folderStatusChangeFolder', "Extension '{0}' changed folders of the workspace", extensionName);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private _onDidChangeWorkspace(): void {
|
||||
this._proxy.$acceptWorkspaceData(this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace());
|
||||
}
|
||||
|
@ -122,4 +168,4 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
|||
return result.results.every(each => each.success === true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -419,6 +419,9 @@ export function createApiFactory(
|
|||
set name(value) {
|
||||
throw errors.readonly();
|
||||
},
|
||||
updateWorkspaceFolders: proposedApiFunction(extension, (index, deleteCount, ...workspaceFoldersToAdd) => {
|
||||
return extHostWorkspace.updateWorkspaceFolders(extension.displayName || extension.name, index, deleteCount, ...workspaceFoldersToAdd);
|
||||
}),
|
||||
onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) {
|
||||
return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables);
|
||||
},
|
||||
|
|
|
@ -349,6 +349,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
|
|||
$startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable<UriComponents[]>;
|
||||
$cancelSearch(requestId: number): Thenable<boolean>;
|
||||
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
|
||||
}
|
||||
|
||||
export interface IFileChangeDto {
|
||||
|
|
|
@ -7,40 +7,107 @@
|
|||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { delta } from 'vs/base/common/arrays';
|
||||
import { delta as arrayDelta } from 'vs/base/common/arrays';
|
||||
import { relative, dirname } from 'path';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
class Workspace2 extends Workspace {
|
||||
function isFolderEqual(folderA: URI, folderB: URI): boolean {
|
||||
return isEqual(folderA, folderB, !isLinux);
|
||||
}
|
||||
|
||||
static fromData(data: IWorkspaceData) {
|
||||
function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return isFolderEqual(a.uri, b.uri) ? 0 : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
if (a.index !== b.index) {
|
||||
return a.index < b.index ? -1 : 1;
|
||||
}
|
||||
|
||||
return isFolderEqual(a.uri, b.uri) ? compare(a.name, b.name) : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder) => number): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } {
|
||||
const oldSortedFolders = oldFolders.slice(0).sort(compare);
|
||||
const newSortedFolders = newFolders.slice(0).sort(compare);
|
||||
|
||||
return arrayDelta(oldSortedFolders, newSortedFolders, compare);
|
||||
}
|
||||
|
||||
interface MutableWorkspaceFolder extends vscode.WorkspaceFolder {
|
||||
name: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
class ExtHostWorkspaceImpl extends Workspace {
|
||||
|
||||
static toExtHostWorkspace(data: IWorkspaceData, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } {
|
||||
if (!data) {
|
||||
return null;
|
||||
} else {
|
||||
const { id, name, folders } = data;
|
||||
return new Workspace2(
|
||||
id,
|
||||
name,
|
||||
folders.map(({ uri, name, index }) => new WorkspaceFolder({ name, index, uri: URI.revive(uri) }))
|
||||
);
|
||||
return { workspace: null, added: [], removed: [] };
|
||||
}
|
||||
|
||||
const { id, name, folders } = data;
|
||||
const newWorkspaceFolders: vscode.WorkspaceFolder[] = [];
|
||||
|
||||
// If we have an existing workspace, we try to find the folders that match our
|
||||
// data and update their properties. It could be that an extension stored them
|
||||
// for later use and we want to keep them "live" if they are still present.
|
||||
const oldWorkspace = previousConfirmedWorkspace;
|
||||
if (oldWorkspace) {
|
||||
folders.forEach((folderData, index) => {
|
||||
const folderUri = URI.revive(folderData.uri);
|
||||
const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri);
|
||||
|
||||
if (existingFolder) {
|
||||
existingFolder.name = folderData.name;
|
||||
existingFolder.index = folderData.index;
|
||||
|
||||
newWorkspaceFolders.push(existingFolder);
|
||||
} else {
|
||||
newWorkspaceFolders.push({ uri: folderUri, name: folderData.name, index });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newWorkspaceFolders.push(...folders.map(({ uri, name, index }) => ({ uri: URI.revive(uri), name, index })));
|
||||
}
|
||||
|
||||
// make sure to restore sort order based on index
|
||||
newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1);
|
||||
|
||||
const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders);
|
||||
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri);
|
||||
|
||||
return { workspace, added, removed };
|
||||
}
|
||||
|
||||
private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder {
|
||||
for (let i = 0; i < workspace.folders.length; i++) {
|
||||
const folder = workspace.workspaceFolders[i];
|
||||
if (isFolderEqual(folder.uri, folderUriToFind)) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private readonly _workspaceFolders: vscode.WorkspaceFolder[] = [];
|
||||
private readonly _structure = TernarySearchTree.forPaths<vscode.WorkspaceFolder>();
|
||||
|
||||
private constructor(id: string, name: string, folders: WorkspaceFolder[]) {
|
||||
super(id, name, folders);
|
||||
private constructor(id: string, name: string, folders: vscode.WorkspaceFolder[]) {
|
||||
super(id, name, folders.map(f => new WorkspaceFolder(f)));
|
||||
|
||||
// setup the workspace folder data structure
|
||||
this.folders.forEach(({ name, uri, index }) => {
|
||||
const workspaceFolder = { name, uri, index };
|
||||
this._workspaceFolders.push(workspaceFolder);
|
||||
this._structure.set(workspaceFolder.uri.toString(), workspaceFolder);
|
||||
folders.forEach(folder => {
|
||||
this._workspaceFolders.push(folder);
|
||||
this._structure.set(folder.uri.toString(), folder);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -63,44 +130,98 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
|||
|
||||
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
|
||||
private readonly _proxy: MainThreadWorkspaceShape;
|
||||
private _workspace: Workspace2;
|
||||
|
||||
private _confirmedWorkspace: ExtHostWorkspaceImpl;
|
||||
private _unconfirmedWorkspace: ExtHostWorkspaceImpl;
|
||||
|
||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||
|
||||
constructor(mainContext: IMainContext, data: IWorkspaceData) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace);
|
||||
this._workspace = Workspace2.fromData(data);
|
||||
this._confirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace(data).workspace;
|
||||
}
|
||||
|
||||
// --- workspace ---
|
||||
|
||||
get workspace(): Workspace {
|
||||
return this._workspace;
|
||||
return this._actualWorkspace;
|
||||
}
|
||||
|
||||
private get _actualWorkspace(): ExtHostWorkspaceImpl {
|
||||
return this._unconfirmedWorkspace || this._confirmedWorkspace;
|
||||
}
|
||||
|
||||
getWorkspaceFolders(): vscode.WorkspaceFolder[] {
|
||||
if (!this._workspace) {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._workspace.workspaceFolders.slice(0);
|
||||
}
|
||||
return this._actualWorkspace.workspaceFolders.slice(0);
|
||||
}
|
||||
|
||||
updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, ...workspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[]): boolean {
|
||||
const validatedDistinctWorkspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[] = [];
|
||||
if (Array.isArray(workspaceFoldersToAdd)) {
|
||||
workspaceFoldersToAdd.forEach(folderToAdd => {
|
||||
if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri))) {
|
||||
validatedDistinctWorkspaceFoldersToAdd.push({ uri: folderToAdd.uri, name: folderToAdd.name || basenameOrAuthority(folderToAdd.uri) });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ([index, deleteCount].some(i => typeof i !== 'number' || i < 0)) {
|
||||
return false; // validate numbers
|
||||
}
|
||||
|
||||
if (deleteCount === 0 && validatedDistinctWorkspaceFoldersToAdd.length === 0) {
|
||||
return false; // nothing to delete or add
|
||||
}
|
||||
|
||||
const currentWorkspaceFolders: MutableWorkspaceFolder[] = this._actualWorkspace ? this._actualWorkspace.workspaceFolders : [];
|
||||
if (index + deleteCount > currentWorkspaceFolders.length) {
|
||||
return false; // cannot delete more than we have
|
||||
}
|
||||
|
||||
const newWorkspaceFolders = currentWorkspaceFolders.slice(0);
|
||||
newWorkspaceFolders.splice(index, deleteCount, ...validatedDistinctWorkspaceFoldersToAdd.map(f => ({ uri: f.uri, name: f.name || basenameOrAuthority(f.uri) })));
|
||||
newWorkspaceFolders.forEach((f, index) => f.index = index); // fix index
|
||||
const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex);
|
||||
if (added.length === 0 && removed.length === 0) {
|
||||
return false; // nothing actually changed
|
||||
}
|
||||
|
||||
// Trigger on main side
|
||||
if (this._proxy) {
|
||||
this._proxy.$updateWorkspaceFolders(extensionName, index, deleteCount, validatedDistinctWorkspaceFoldersToAdd).then(null, onUnexpectedError);
|
||||
}
|
||||
|
||||
// Try to accept directly
|
||||
const accepted = this.trySetWorkspaceData({
|
||||
id: this._actualWorkspace.id,
|
||||
name: this._actualWorkspace.name,
|
||||
configuration: this._actualWorkspace.configuration,
|
||||
folders: newWorkspaceFolders
|
||||
} as IWorkspaceData);
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder {
|
||||
if (!this._workspace) {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._workspace.getWorkspaceFolder(uri, resolveParent);
|
||||
return this._actualWorkspace.getWorkspaceFolder(uri, resolveParent);
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
|
||||
// this is legacy from the days before having
|
||||
// multi-root and we keep it only alive if there
|
||||
// is just one workspace folder.
|
||||
if (!this._workspace) {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
const { folders } = this._workspace;
|
||||
|
||||
const { folders } = this._actualWorkspace;
|
||||
if (folders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -130,7 +251,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
|||
}
|
||||
|
||||
if (typeof includeWorkspace === 'undefined') {
|
||||
includeWorkspace = this.workspace.folders.length > 1;
|
||||
includeWorkspace = this._actualWorkspace.folders.length > 1;
|
||||
}
|
||||
|
||||
let result = relative(folder.uri.fsPath, path);
|
||||
|
@ -140,27 +261,35 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
|||
return normalize(result, true);
|
||||
}
|
||||
|
||||
private trySetWorkspaceData(data: IWorkspaceData): boolean {
|
||||
|
||||
// Update directly here. The workspace is unconfirmed as long as we did not get an
|
||||
// acknowledgement from the main side (via $acceptWorkspaceData)
|
||||
if (this._actualWorkspace) {
|
||||
this._unconfirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._actualWorkspace).workspace;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$acceptWorkspaceData(data: IWorkspaceData): void {
|
||||
|
||||
// keep old workspace folder, build new workspace, and
|
||||
// capture new workspace folders. Compute delta between
|
||||
// them send that as event
|
||||
const oldRoots = this._workspace ? this._workspace.workspaceFolders.sort(ExtHostWorkspace._compareWorkspaceFolder) : [];
|
||||
const { workspace, added, removed } = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._confirmedWorkspace, this._unconfirmedWorkspace);
|
||||
|
||||
this._workspace = Workspace2.fromData(data);
|
||||
const newRoots = this._workspace ? this._workspace.workspaceFolders.sort(ExtHostWorkspace._compareWorkspaceFolder) : [];
|
||||
// Update our workspace object. We have a confirmed workspace, so we drop our
|
||||
// unconfirmed workspace.
|
||||
this._confirmedWorkspace = workspace;
|
||||
this._unconfirmedWorkspace = undefined;
|
||||
|
||||
const { added, removed } = delta(oldRoots, newRoots, ExtHostWorkspace._compareWorkspaceFolder);
|
||||
// Events
|
||||
this._onDidChangeWorkspace.fire(Object.freeze({
|
||||
added: Object.freeze<vscode.WorkspaceFolder[]>(added),
|
||||
removed: Object.freeze<vscode.WorkspaceFolder[]>(removed)
|
||||
}));
|
||||
}
|
||||
|
||||
private static _compareWorkspaceFolder(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
// --- search ---
|
||||
|
||||
findFiles(include: vscode.GlobPattern, exclude: vscode.GlobPattern, maxResults?: number, token?: vscode.CancellationToken): Thenable<vscode.Uri[]> {
|
||||
|
|
|
@ -110,9 +110,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
|
|||
return this.workspace.getFolder(resource);
|
||||
}
|
||||
|
||||
public addFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
|
||||
public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
|
||||
assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
|
||||
return this.workspaceEditingQueue.queue(() => this.doAddFolders(foldersToAdd));
|
||||
return this.workspaceEditingQueue.queue(() => this.doAddFolders(foldersToAdd, index));
|
||||
}
|
||||
|
||||
public removeFolders(foldersToRemove: URI[]): TPromise<void> {
|
||||
|
@ -134,7 +134,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
|
|||
return false;
|
||||
}
|
||||
|
||||
private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
|
||||
private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
|
||||
if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
|
||||
return TPromise.as(void 0); // we need a workspace to begin with
|
||||
}
|
||||
|
@ -176,7 +176,16 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
|
|||
});
|
||||
|
||||
if (storedFoldersToAdd.length > 0) {
|
||||
return this.setFolders([...currentStoredFolders, ...storedFoldersToAdd]);
|
||||
let newStoredWorkspaceFolders: IStoredWorkspaceFolder[] = [];
|
||||
|
||||
if (typeof index === 'number' && index >= 0 && index < currentStoredFolders.length) {
|
||||
newStoredWorkspaceFolders = currentStoredFolders.slice(0);
|
||||
newStoredWorkspaceFolders.splice(index, 0, ...storedFoldersToAdd);
|
||||
} else {
|
||||
newStoredWorkspaceFolders = [...currentStoredFolders, ...storedFoldersToAdd];
|
||||
}
|
||||
|
||||
return this.setFolders(newStoredWorkspaceFolders);
|
||||
}
|
||||
|
||||
return TPromise.as(void 0);
|
||||
|
|
|
@ -192,6 +192,34 @@ suite('WorkspaceContextService - Workspace', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('add folders (at specific index)', () => {
|
||||
const workspaceDir = path.dirname(testObject.getWorkspace().folders[0].uri.fsPath);
|
||||
return testObject.addFolders([{ uri: URI.file(path.join(workspaceDir, 'd')) }, { uri: URI.file(path.join(workspaceDir, 'c')) }], 0)
|
||||
.then(() => {
|
||||
const actual = testObject.getWorkspace().folders;
|
||||
|
||||
assert.equal(actual.length, 4);
|
||||
assert.equal(path.basename(actual[0].uri.fsPath), 'd');
|
||||
assert.equal(path.basename(actual[1].uri.fsPath), 'c');
|
||||
assert.equal(path.basename(actual[2].uri.fsPath), 'a');
|
||||
assert.equal(path.basename(actual[3].uri.fsPath), 'b');
|
||||
});
|
||||
});
|
||||
|
||||
test('add folders (at specific wrong index)', () => {
|
||||
const workspaceDir = path.dirname(testObject.getWorkspace().folders[0].uri.fsPath);
|
||||
return testObject.addFolders([{ uri: URI.file(path.join(workspaceDir, 'd')) }, { uri: URI.file(path.join(workspaceDir, 'c')) }], 10)
|
||||
.then(() => {
|
||||
const actual = testObject.getWorkspace().folders;
|
||||
|
||||
assert.equal(actual.length, 4);
|
||||
assert.equal(path.basename(actual[0].uri.fsPath), 'a');
|
||||
assert.equal(path.basename(actual[1].uri.fsPath), 'b');
|
||||
assert.equal(path.basename(actual[2].uri.fsPath), 'd');
|
||||
assert.equal(path.basename(actual[3].uri.fsPath), 'c');
|
||||
});
|
||||
});
|
||||
|
||||
test('add folders (with name)', () => {
|
||||
const workspaceDir = path.dirname(testObject.getWorkspace().folders[0].uri.fsPath);
|
||||
return testObject.addFolders([{ uri: URI.file(path.join(workspaceDir, 'd')), name: 'DDD' }, { uri: URI.file(path.join(workspaceDir, 'c')), name: 'CCC' }])
|
||||
|
|
|
@ -27,6 +27,12 @@ export interface IWorkspaceEditingService {
|
|||
*/
|
||||
removeFolders(folders: URI[], donotNotifyError?: boolean): TPromise<void>;
|
||||
|
||||
/**
|
||||
* Allows to add and remove folders to the existing workspace at once.
|
||||
* When `donotNotifyError` is `true`, error will be bubbled up otherwise, the service handles the error with proper message and action
|
||||
*/
|
||||
updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void>;
|
||||
|
||||
/**
|
||||
* creates a new workspace with the provided folders and opens it. if path is provided
|
||||
* the workspace will be saved into that location.
|
||||
|
|
|
@ -47,16 +47,56 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
|||
) {
|
||||
}
|
||||
|
||||
public updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void> {
|
||||
const folders = this.contextService.getWorkspace().folders;
|
||||
|
||||
let foldersToDelete: URI[] = [];
|
||||
if (typeof deleteCount === 'number') {
|
||||
foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri);
|
||||
}
|
||||
|
||||
const wantsToDelete = foldersToDelete.length > 0;
|
||||
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
|
||||
if (wantsToAdd && !wantsToDelete) {
|
||||
return this.doAddFolders(foldersToAdd, index, donotNotifyError);
|
||||
}
|
||||
|
||||
// Delete Folders
|
||||
if (wantsToDelete && !wantsToAdd) {
|
||||
return this.removeFolders(foldersToDelete);
|
||||
}
|
||||
|
||||
// Add & Delete Folders
|
||||
if (this.includesSingleFolderWorkspace(foldersToDelete)) {
|
||||
// if we are in single-folder state and the folder is replaced with
|
||||
// other folders, we handle this specially and just enter workspace
|
||||
// mode with the folders that are being added.
|
||||
return this.createAndEnterWorkspace(foldersToAdd);
|
||||
}
|
||||
|
||||
// Make sure to first remove folders and then add them to account for folders being updated
|
||||
return this.removeFolders(foldersToDelete).then(() => this.doAddFolders(foldersToAdd, index, donotNotifyError));
|
||||
}
|
||||
|
||||
public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): TPromise<void> {
|
||||
return this.doAddFolders(foldersToAdd, void 0, donotNotifyError);
|
||||
}
|
||||
|
||||
private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): TPromise<void> {
|
||||
const state = this.contextService.getWorkbenchState();
|
||||
|
||||
// If we are in no-workspace or single-folder workspace, adding folders has to
|
||||
// enter a workspace.
|
||||
if (state !== WorkbenchState.WORKSPACE) {
|
||||
const newWorkspaceFolders: IWorkspaceFolderCreationData[] = distinct([
|
||||
...this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData)),
|
||||
...foldersToAdd
|
||||
] as IWorkspaceFolderCreationData[], folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
|
||||
let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData));
|
||||
newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd);
|
||||
newWorkspaceFolders = distinct(newWorkspaceFolders, folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
|
||||
|
||||
if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
|
||||
return TPromise.as(void 0); // return if the operation is a no-op for the current state
|
||||
|
@ -66,19 +106,16 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
|||
}
|
||||
|
||||
// Delegate addition of folders to workspace service otherwise
|
||||
return this.contextService.addFolders(foldersToAdd)
|
||||
return this.contextService.addFolders(foldersToAdd, index)
|
||||
.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
|
||||
}
|
||||
|
||||
public removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): TPromise<void> {
|
||||
|
||||
// If we are in single-folder state and the opened folder is to be removed,
|
||||
// we close the workspace and enter the empty workspace state for the window.
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
|
||||
const workspaceFolder = this.contextService.getWorkspace().folders[0];
|
||||
if (foldersToRemove.some(folder => isEqual(folder, workspaceFolder.uri, !isLinux))) {
|
||||
return this.windowService.closeWorkspace();
|
||||
}
|
||||
// we create an empty workspace and enter it.
|
||||
if (this.includesSingleFolderWorkspace(foldersToRemove)) {
|
||||
return this.createAndEnterWorkspace([]);
|
||||
}
|
||||
|
||||
// Delegate removal of folders to workspace service otherwise
|
||||
|
@ -86,6 +123,15 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
|||
.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
|
||||
}
|
||||
|
||||
private includesSingleFolderWorkspace(folders: URI[]): boolean {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
|
||||
const workspaceFolder = this.contextService.getWorkspace().folders[0];
|
||||
return (folders.some(folder => isEqual(folder, workspaceFolder.uri, !isLinux)));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public createAndEnterWorkspace(folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<void> {
|
||||
return this.doEnterWorkspace(() => this.windowService.createAndEnterWorkspace(folders, path));
|
||||
}
|
||||
|
|
|
@ -159,57 +159,382 @@ suite('ExtHostWorkspace', function () {
|
|||
assert.equal(folder.name, 'Two');
|
||||
});
|
||||
|
||||
test('Multiroot change event should have a delta, #29641', function () {
|
||||
test('Multiroot change event should have a delta, #29641', function (done) {
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] });
|
||||
|
||||
let finished = false;
|
||||
const finish = (error?) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
done(error);
|
||||
}
|
||||
};
|
||||
|
||||
let sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.deepEqual(e.removed, []);
|
||||
try {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.deepEqual(e.removed, []);
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [] });
|
||||
sub.dispose();
|
||||
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar');
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar');
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] });
|
||||
sub.dispose();
|
||||
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar2');
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar2');
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar2'), 1)] });
|
||||
sub.dispose();
|
||||
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.equal(e.removed.length, 2);
|
||||
assert.equal(e.removed[0].uri.toString(), 'foo:bar');
|
||||
assert.equal(e.removed[1].uri.toString(), 'foo:bar2');
|
||||
try {
|
||||
assert.equal(e.removed.length, 2);
|
||||
assert.equal(e.removed[0].uri.toString(), 'foo:bar');
|
||||
assert.equal(e.removed[1].uri.toString(), 'foo:bar2');
|
||||
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar3');
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar3');
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] });
|
||||
sub.dispose();
|
||||
|
||||
finish();
|
||||
});
|
||||
|
||||
test('Multiroot change event is immutable', function () {
|
||||
test('Multiroot change keeps existing workspaces live', function () {
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] });
|
||||
|
||||
let firstFolder = ws.getWorkspaceFolders()[0];
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar2'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1, 'renamed')] });
|
||||
|
||||
assert.equal(ws.getWorkspaceFolders()[1], firstFolder);
|
||||
assert.equal(firstFolder.index, 1);
|
||||
assert.equal(firstFolder.name, 'renamed');
|
||||
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar2'), 1), aWorkspaceFolderData(URI.parse('foo:bar'), 2)] });
|
||||
assert.equal(ws.getWorkspaceFolders()[2], firstFolder);
|
||||
assert.equal(firstFolder.index, 2);
|
||||
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] });
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1)] });
|
||||
|
||||
assert.notEqual(firstFolder, ws.workspace.folders[0]);
|
||||
});
|
||||
|
||||
test('updateWorkspaceFolders - invalid arguments', function () {
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] });
|
||||
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', null, null));
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', 0, 0));
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', 0, 1));
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', 1, 0));
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', -1, 0));
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', -1, -1));
|
||||
|
||||
ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] });
|
||||
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', 1, 1));
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', 0, 2));
|
||||
assert.equal(false, ws.updateWorkspaceFolders('ext', 0, 1, asUpdateWorkspaceFolderData(URI.parse('foo:bar'))));
|
||||
});
|
||||
|
||||
test('updateWorkspaceFolders - valid arguments', function (done) {
|
||||
let finished = false;
|
||||
const finish = (error?) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
done(error);
|
||||
}
|
||||
};
|
||||
|
||||
const protocol = {
|
||||
getProxy: () => { return undefined; },
|
||||
set: undefined,
|
||||
assertRegistered: undefined
|
||||
};
|
||||
|
||||
const ws = new ExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] });
|
||||
|
||||
//
|
||||
// Add one folder
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 0, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar'))));
|
||||
assert.equal(1, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
|
||||
const firstAddedFolder = ws.getWorkspaceFolders()[0];
|
||||
|
||||
let gotEvent = false;
|
||||
let sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar');
|
||||
assert.equal(e.added[0], firstAddedFolder); // verify object is still live
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Add two more folders
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 1, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar1')), asUpdateWorkspaceFolderData(URI.parse('foo:bar2'))));
|
||||
assert.equal(3, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
|
||||
assert.equal(ws.workspace.folders[2].uri.toString(), URI.parse('foo:bar2').toString());
|
||||
|
||||
const secondAddedFolder = ws.getWorkspaceFolders()[1];
|
||||
const thirdAddedFolder = ws.getWorkspaceFolders()[2];
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 2);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar1');
|
||||
assert.equal(e.added[1].uri.toString(), 'foo:bar2');
|
||||
assert.equal(e.added[0], secondAddedFolder);
|
||||
assert.equal(e.added[1], thirdAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1), aWorkspaceFolderData(URI.parse('foo:bar2'), 2)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[2], thirdAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Remove one folder
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 2, 1));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.equal(e.removed.length, 1);
|
||||
assert.equal(e.removed[0], thirdAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Rename folder
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar'), 'renamed 1'), asUpdateWorkspaceFolderData(URI.parse('foo:bar1'), 'renamed 2')));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
|
||||
assert.equal(ws.workspace.folders[0].name, 'renamed 1');
|
||||
assert.equal(ws.workspace.folders[1].name, 'renamed 2');
|
||||
assert.equal(ws.getWorkspaceFolders()[0].name, 'renamed 1');
|
||||
assert.equal(ws.getWorkspaceFolders()[1].name, 'renamed 2');
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.equal(e.removed.length, []);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0, 'renamed 1'), aWorkspaceFolderData(URI.parse('foo:bar1'), 1, 'renamed 2')] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live
|
||||
assert.equal(ws.workspace.folders[0].name, 'renamed 1');
|
||||
assert.equal(ws.workspace.folders[1].name, 'renamed 2');
|
||||
assert.equal(ws.getWorkspaceFolders()[0].name, 'renamed 1');
|
||||
assert.equal(ws.getWorkspaceFolders()[1].name, 'renamed 2');
|
||||
|
||||
//
|
||||
// Add and remove folders
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar3')), asUpdateWorkspaceFolderData(URI.parse('foo:bar4'))));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar3').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar4').toString());
|
||||
|
||||
const fourthAddedFolder = ws.getWorkspaceFolders()[0];
|
||||
const fifthAddedFolder = ws.getWorkspaceFolders()[1];
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.equal(e.added.length, 2);
|
||||
assert.equal(e.added[0], fourthAddedFolder);
|
||||
assert.equal(e.added[1], fifthAddedFolder);
|
||||
assert.equal(e.removed.length, 2);
|
||||
assert.equal(e.removed[0], firstAddedFolder);
|
||||
assert.equal(e.removed[1], secondAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar4'), 1)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fourthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fifthAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Swap folders
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar4')), asUpdateWorkspaceFolderData(URI.parse('foo:bar3'))));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar4').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar3').toString());
|
||||
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.equal(e.added.length, 0);
|
||||
assert.equal(e.removed.length, 0);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar4'), 0), aWorkspaceFolderData(URI.parse('foo:bar3'), 1)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live
|
||||
assert.equal(fifthAddedFolder.index, 0);
|
||||
assert.equal(fourthAddedFolder.index, 1);
|
||||
|
||||
//
|
||||
// Add one folder after the other without waiting for confirmation
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 2, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar5'))));
|
||||
|
||||
assert.equal(3, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar4').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar3').toString());
|
||||
assert.equal(ws.workspace.folders[2].uri.toString(), URI.parse('foo:bar5').toString());
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders('ext', 3, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar6'))));
|
||||
|
||||
assert.equal(4, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar4').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar3').toString());
|
||||
assert.equal(ws.workspace.folders[2].uri.toString(), URI.parse('foo:bar5').toString());
|
||||
assert.equal(ws.workspace.folders[3].uri.toString(), URI.parse('foo:bar6').toString());
|
||||
|
||||
const sixthAddedFolder = ws.getWorkspaceFolders()[2];
|
||||
const seventhAddedFolder = ws.getWorkspaceFolders()[3];
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.equal(e.added.length, 2);
|
||||
assert.equal(e.added[0], sixthAddedFolder);
|
||||
assert.equal(e.added[1], seventhAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({
|
||||
id: 'foo', name: 'Test', folders: [
|
||||
aWorkspaceFolderData(URI.parse('foo:bar4'), 0),
|
||||
aWorkspaceFolderData(URI.parse('foo:bar3'), 1),
|
||||
aWorkspaceFolderData(URI.parse('foo:bar5'), 2),
|
||||
aWorkspaceFolderData(URI.parse('foo:bar6'), 3)
|
||||
]
|
||||
}); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[2], sixthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[3], seventhAddedFolder); // verify object is still live
|
||||
|
||||
finish();
|
||||
});
|
||||
|
||||
test('Multiroot change event is immutable', function (done) {
|
||||
let finished = false;
|
||||
const finish = (error?) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
done(error);
|
||||
}
|
||||
};
|
||||
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] });
|
||||
let sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.throws(() => {
|
||||
(<any>e).added = [];
|
||||
});
|
||||
assert.throws(() => {
|
||||
(<any>e.added)[0] = null;
|
||||
});
|
||||
try {
|
||||
assert.throws(() => {
|
||||
(<any>e).added = [];
|
||||
});
|
||||
assert.throws(() => {
|
||||
(<any>e.added)[0] = null;
|
||||
});
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [] });
|
||||
sub.dispose();
|
||||
finish();
|
||||
});
|
||||
|
||||
test('`vscode.workspace.getWorkspaceFolder(file)` don\'t return workspace folder when file open from command line. #36221', function () {
|
||||
|
@ -230,4 +555,8 @@ suite('ExtHostWorkspace', function () {
|
|||
name: name || basename(uri.path)
|
||||
};
|
||||
}
|
||||
|
||||
function asUpdateWorkspaceFolderData(uri: URI, name?: string): { uri: URI, name?: string } {
|
||||
return { uri, name };
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue