From 908a15d6a8766a3e2daf733087feda468032d8eb Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Tue, 15 Sep 2020 11:12:30 -0700 Subject: [PATCH] remove project feature (#12297) * remove project feature * update test --- extensions/data-workspace/package.json | 15 +++++++++++ extensions/data-workspace/package.nls.json | 3 ++- .../data-workspace/src/common/interfaces.ts | 11 ++++++++ .../src/common/workspaceTreeDataProvider.ts | 6 ++++- .../data-workspace/src/dataworkspace.d.ts | 6 +++++ extensions/data-workspace/src/main.ts | 6 ++++- .../src/services/workspaceService.ts | 16 +++++++++++ .../src/test/projectProviderRegistry.test.ts | 3 +++ .../src/test/workspaceService.test.ts | 27 +++++++++++++++++++ .../test/workspaceTreeDataProvider.test.ts | 3 +++ extensions/sql-database-projects/package.json | 2 +- .../src/projectProvider/projectProvider.ts | 10 +++++++ 12 files changed, 104 insertions(+), 4 deletions(-) diff --git a/extensions/data-workspace/package.json b/extensions/data-workspace/package.json index eb1d463ab0..1342eabb03 100644 --- a/extensions/data-workspace/package.json +++ b/extensions/data-workspace/package.json @@ -46,6 +46,10 @@ "title": "%refresh-workspace-command%", "category": "", "icon": "$(refresh)" + }, + { + "command": "projects.removeProject", + "title": "%remove-project-command%" } ], "menus": { @@ -69,6 +73,17 @@ { "command": "dataworkspace.refresh", "when": "false" + }, + { + "command": "projects.removeProject", + "when": "false" + } + ], + "view/item/context": [ + { + "command": "projects.removeProject", + "when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.project", + "group": "9_dbProjectsLast@9" } ] }, diff --git a/extensions/data-workspace/package.nls.json b/extensions/data-workspace/package.nls.json index c9e8199e2a..a76bec3655 100644 --- a/extensions/data-workspace/package.nls.json +++ b/extensions/data-workspace/package.nls.json @@ -4,5 +4,6 @@ "data-workspace-view-container-name": "Projects", "main-view-name": "Projects", "add-project-command": "Add Project", - "refresh-workspace-command": "Refresh" + "refresh-workspace-command": "Refresh", + "remove-project-command":"Remove Project" } diff --git a/extensions/data-workspace/src/common/interfaces.ts b/extensions/data-workspace/src/common/interfaces.ts index 8cba0ad109..9f6875d4d0 100644 --- a/extensions/data-workspace/src/common/interfaces.ts +++ b/extensions/data-workspace/src/common/interfaces.ts @@ -58,6 +58,17 @@ export interface IWorkspaceService { * @param projectFiles the list of project files to be added, the project file should be absolute path. */ addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise; + + /** + * Remove the project from workspace + * @param projectFile The project file to be removed + */ + removeProject(projectFile: vscode.Uri): Promise; + + /** + * Event fires when projects in workspace changes + */ + readonly onDidWorkspaceProjectsChange: vscode.Event; } /** diff --git a/extensions/data-workspace/src/common/workspaceTreeDataProvider.ts b/extensions/data-workspace/src/common/workspaceTreeDataProvider.ts index e8ed4bb930..d9d3686ddd 100644 --- a/extensions/data-workspace/src/common/workspaceTreeDataProvider.ts +++ b/extensions/data-workspace/src/common/workspaceTreeDataProvider.ts @@ -11,7 +11,11 @@ import { UnknownProjectsErrorMessage } from './constants'; * Tree data provider for the workspace main view */ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider{ - constructor(private _workspaceService: IWorkspaceService) { } + constructor(private _workspaceService: IWorkspaceService) { + this._workspaceService.onDidWorkspaceProjectsChange(() => { + this.refresh(); + }); + } private _onDidChangeTreeData: vscode.EventEmitter | undefined = new vscode.EventEmitter(); readonly onDidChangeTreeData?: vscode.Event | undefined = this._onDidChangeTreeData?.event; diff --git a/extensions/data-workspace/src/dataworkspace.d.ts b/extensions/data-workspace/src/dataworkspace.d.ts index 98dee292fd..99dc286075 100644 --- a/extensions/data-workspace/src/dataworkspace.d.ts +++ b/extensions/data-workspace/src/dataworkspace.d.ts @@ -31,6 +31,12 @@ declare module 'dataworkspace' { */ getProjectTreeDataProvider(projectFile: vscode.Uri): Promise>; + /** + * Notify the project provider extension that the specified project file has been removed from the data workspace + * @param projectFile The Uri of the project file + */ + RemoveProject(projectFile: vscode.Uri): Promise; + /** * Gets the supported project types */ diff --git a/extensions/data-workspace/src/main.ts b/extensions/data-workspace/src/main.ts index f0537e92a7..cc07c516a7 100644 --- a/extensions/data-workspace/src/main.ts +++ b/extensions/data-workspace/src/main.ts @@ -10,6 +10,7 @@ import { WorkspaceTreeDataProvider } from './common/workspaceTreeDataProvider'; import { WorkspaceService } from './services/workspaceService'; import { DataWorkspaceExtension } from './dataWorkspaceExtension'; import { SelectProjectFileActionName } from './common/constants'; +import { WorkspaceTreeItem } from './common/interfaces'; export async function activate(context: vscode.ExtensionContext): Promise { const workspaceService = new WorkspaceService(); @@ -36,7 +37,6 @@ export async function activate(context: vscode.ExtensionContext): Promise { + await workspaceService.removeProject(vscode.Uri.file(treeItem.element.project.projectFilePath)); + })); + return new DataWorkspaceExtension(); } diff --git a/extensions/data-workspace/src/services/workspaceService.ts b/extensions/data-workspace/src/services/workspaceService.ts index 5126f470e0..a92c3a6295 100644 --- a/extensions/data-workspace/src/services/workspaceService.ts +++ b/extensions/data-workspace/src/services/workspaceService.ts @@ -15,6 +15,9 @@ const WorkspaceConfigurationName = 'dataworkspace'; const ProjectsConfigurationName = 'projects'; export class WorkspaceService implements IWorkspaceService { + private _onDidWorkspaceProjectsChange: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidWorkspaceProjectsChange: vscode.Event = this._onDidWorkspaceProjectsChange?.event; + async addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise { if (vscode.workspace.workspaceFile) { const currentProjects: vscode.Uri[] = await this.getProjectsInWorkspace(); @@ -37,6 +40,7 @@ export class WorkspaceService implements IWorkspaceService { if (newProjectFileAdded) { // Save the new set of projects to the workspace configuration. await this.setWorkspaceConfigurationValue(ProjectsConfigurationName, currentProjects.map(project => this.toRelativePath(project))); + this._onDidWorkspaceProjectsChange.fire(); } if (newWorkspaceFolders.length > 0) { @@ -68,6 +72,18 @@ export class WorkspaceService implements IWorkspaceService { return ProjectProviderRegistry.getProviderByProjectType(projectType); } + async removeProject(projectFile: vscode.Uri): Promise { + if (vscode.workspace.workspaceFile) { + const currentProjects: vscode.Uri[] = await this.getProjectsInWorkspace(); + const projectIdx = currentProjects.findIndex((p: vscode.Uri) => p.fsPath === projectFile.fsPath); + if (projectIdx !== -1) { + currentProjects.splice(projectIdx, 1); + await this.setWorkspaceConfigurationValue(ProjectsConfigurationName, currentProjects.map(project => this.toRelativePath(project))); + this._onDidWorkspaceProjectsChange.fire(); + } + } + } + /** * Ensure the project provider extension for the specified project is loaded * @param projectType The file extension of the project, if not specified, all project provider extensions will be loaded. diff --git a/extensions/data-workspace/src/test/projectProviderRegistry.test.ts b/extensions/data-workspace/src/test/projectProviderRegistry.test.ts index e88cd15218..6f3100be14 100644 --- a/extensions/data-workspace/src/test/projectProviderRegistry.test.ts +++ b/extensions/data-workspace/src/test/projectProviderRegistry.test.ts @@ -23,6 +23,9 @@ export function createProjectProvider(projectTypes: IProjectType[]): IProjectPro const treeDataProvider = new MockTreeDataProvider(); const projectProvider: IProjectProvider = { supportedProjectTypes: projectTypes, + RemoveProject: (projectFile: vscode.Uri): Promise => { + return Promise.resolve(); + }, getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise> => { return Promise.resolve(treeDataProvider); } diff --git a/extensions/data-workspace/src/test/workspaceService.test.ts b/extensions/data-workspace/src/test/workspaceService.test.ts index c1a5e5ff75..4213ae3931 100644 --- a/extensions/data-workspace/src/test/workspaceService.test.ts +++ b/extensions/data-workspace/src/test/workspaceService.test.ts @@ -186,6 +186,10 @@ suite('WorkspaceService Tests', function (): void { stubWorkspaceFile(DefaultWorkspaceFilePath); const updateConfigurationStub = sinon.stub(); const getConfigurationStub = sinon.stub().returns([processPath('folder1/proj2.sqlproj')]); + const onWorkspaceProjectsChangedStub = sinon.stub(); + const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => { + onWorkspaceProjectsChangedStub(); + }); stubGetConfigurationValue(getConfigurationStub, updateConfigurationStub); const asRelativeStub = sinon.stub(vscode.workspace, 'asRelativePath'); sinon.stub(vscode.workspace, 'workspaceFolders').value(['.']); @@ -207,5 +211,28 @@ suite('WorkspaceService Tests', function (): void { should.strictEqual(updateWorkspaceFoldersStub.calledWith(1, null, sinon.match((arg) => { return arg.uri.path === '/test/other'; })), true, 'updateWorkspaceFolder parameters does not match expectation'); + should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired'); + onWorkspaceProjectsChangedDisposable.dispose(); }); + + test('test removeProject', async () => { + const processPath = (original: string): string => { + return original.replace(/\//g, path.sep); + }; + stubWorkspaceFile(DefaultWorkspaceFilePath); + const updateConfigurationStub = sinon.stub(); + const getConfigurationStub = sinon.stub().returns([processPath('folder1/proj2.sqlproj'), processPath('folder2/proj3.sqlproj')]); + const onWorkspaceProjectsChangedStub = sinon.stub(); + const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => { + onWorkspaceProjectsChangedStub(); + }); + stubGetConfigurationValue(getConfigurationStub, updateConfigurationStub); + await service.removeProject(vscode.Uri.file('/test/folder/folder1/proj2.sqlproj')); + should.strictEqual(updateConfigurationStub.calledWith('projects', sinon.match.array.deepEquals([ + processPath('folder2/proj3.sqlproj') + ]), vscode.ConfigurationTarget.Workspace), true, 'updateConfiguration parameters does not match expectation for remove project'); + should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired'); + onWorkspaceProjectsChangedDisposable.dispose(); + }); + }); diff --git a/extensions/data-workspace/src/test/workspaceTreeDataProvider.test.ts b/extensions/data-workspace/src/test/workspaceTreeDataProvider.test.ts index e3b8b59ca8..db6e31b577 100644 --- a/extensions/data-workspace/src/test/workspaceTreeDataProvider.test.ts +++ b/extensions/data-workspace/src/test/workspaceTreeDataProvider.test.ts @@ -71,6 +71,9 @@ suite('workspaceTreeDataProvider Tests', function (): void { icon: '', displayName: 'sql project' }], + RemoveProject: (projectFile: vscode.Uri): Promise => { + return Promise.resolve(); + }, getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise> => { return Promise.resolve(treeDataProvider); } diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json index 4d9b11c8f5..d37beff938 100644 --- a/extensions/sql-database-projects/package.json +++ b/extensions/sql-database-projects/package.json @@ -342,7 +342,7 @@ }, { "command": "sqlDatabaseProjects.close", - "when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project", + "when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project", "group": "9_dbProjectsLast@9" } ], diff --git a/extensions/sql-database-projects/src/projectProvider/projectProvider.ts b/extensions/sql-database-projects/src/projectProvider/projectProvider.ts index 6ba2d64c25..871db897ee 100644 --- a/extensions/sql-database-projects/src/projectProvider/projectProvider.ts +++ b/extensions/sql-database-projects/src/projectProvider/projectProvider.ts @@ -24,6 +24,16 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide return provider; } + /** + * Callback method when a project has been removed from the workspace view + * @param projectFile The Uri of the project file + */ + RemoveProject(projectFile: vscode.Uri): Promise { + // No resource release needed + console.log(`project file unloaded: ${projectFile.fsPath}`); + return Promise.resolve(); + } + /** * Gets the supported project types */