mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Make project workspace selectable if no workspace is open yet (#13508)
* allow new workspace location to be editable * fix workspace inputbox not showing up after toggling open workspace radio buttons * add a few tests * cleanup * fix errors * addressing comments * fix filter for windows * add error message if existing workspace file is selected and change picker to be folder only * address comments * fix typos and update tests
This commit is contained in:
@@ -22,6 +22,7 @@ export const EnterWorkspaceConfirmation = localize('dataworkspace.enterWorkspace
|
|||||||
export const OkButtonText = localize('dataworkspace.ok', "OK");
|
export const OkButtonText = localize('dataworkspace.ok', "OK");
|
||||||
export const CancelButtonText = localize('dataworkspace.cancel', "Cancel");
|
export const CancelButtonText = localize('dataworkspace.cancel', "Cancel");
|
||||||
export const BrowseButtonText = localize('dataworkspace.browse', "Browse");
|
export const BrowseButtonText = localize('dataworkspace.browse', "Browse");
|
||||||
|
export const WorkspaceFileExtension = '.code-workspace';
|
||||||
export const DefaultInputWidth = '400px';
|
export const DefaultInputWidth = '400px';
|
||||||
export const DefaultButtonWidth = '80px';
|
export const DefaultButtonWidth = '80px';
|
||||||
|
|
||||||
@@ -35,19 +36,20 @@ export const ProjectLocationPlaceholder = localize('dataworkspace.projectLocatio
|
|||||||
export const AddProjectToCurrentWorkspace = localize('dataworkspace.AddProjectToCurrentWorkspace', "This project will be added to the current workspace.");
|
export const AddProjectToCurrentWorkspace = localize('dataworkspace.AddProjectToCurrentWorkspace', "This project will be added to the current workspace.");
|
||||||
export const NewWorkspaceWillBeCreated = localize('dataworkspace.NewWorkspaceWillBeCreated', "A new workspace will be created for this project.");
|
export const NewWorkspaceWillBeCreated = localize('dataworkspace.NewWorkspaceWillBeCreated', "A new workspace will be created for this project.");
|
||||||
export const WorkspaceLocationTitle = localize('dataworkspace.workspaceLocationTitle', "Workspace location");
|
export const WorkspaceLocationTitle = localize('dataworkspace.workspaceLocationTitle', "Workspace location");
|
||||||
export const ProjectParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.projectParentDirectoryNotExistError', "The selected location: '{0}' does not exist or is not a directory.", location); };
|
export const ProjectParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.projectParentDirectoryNotExistError', "The selected project location '{0}' does not exist or is not a directory.", location); };
|
||||||
export const ProjectDirectoryAlreadyExistError = (projectName: string, location: string): string => { return localize('dataworkspace.projectDirectoryAlreadyExistError', "There is already a directory named '{0}' in the selected location: '{1}'.", projectName, location); };
|
export const ProjectDirectoryAlreadyExistError = (projectName: string, location: string): string => { return localize('dataworkspace.projectDirectoryAlreadyExistError', "There is already a directory named '{0}' in the selected location: '{1}'.", projectName, location); };
|
||||||
|
export const WorkspaceFileInvalidError = (workspace: string): string => { return localize('dataworkspace.workspaceFileInvalidError', "The selected workspace file path '{0}' does not have the required file extension {1}", workspace, WorkspaceFileExtension); };
|
||||||
|
export const WorkspaceParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.workspaceParentDirectoryNotExistError', "The selected workspace location '{0}' does not exist or is not a directory", location); };
|
||||||
|
export const WorkspaceFileAlreadyExistsError = (file: string): string => { return localize('dataworkspace.workspaceFileAlreadyExistsError', "The selected workspace file '{0}' already exists. To add the project to an existing workspace, use the Open Existing dialog to first open the workspace", file); };
|
||||||
|
|
||||||
//Open Existing Dialog
|
//Open Existing Dialog
|
||||||
export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open existing");
|
export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open existing");
|
||||||
export const ProjectFileNotExistError = (projectFilePath: string): string => { return localize('dataworkspace.projectFileNotExistError', "The selected project file '{0}' does not exist or is not a file.", projectFilePath); };
|
export const FileNotExistError = (fileType: string, filePath: string): string => { return localize('dataworkspace.fileNotExistError', "The selected {0} file '{1}' does not exist or is not a file.", fileType, filePath); };
|
||||||
export const WorkspaceFileNotExistError = (workspaceFilePath: string): string => { return localize('dataworkspace.workspaceFileNotExistError', "The selected workspace file '{0}' does not exist or is not a file.", workspaceFilePath); };
|
|
||||||
export const Project = localize('dataworkspace.project', "Project");
|
export const Project = localize('dataworkspace.project', "Project");
|
||||||
export const Workspace = localize('dataworkspace.workspace', "Workspace");
|
export const Workspace = localize('dataworkspace.workspace', "Workspace");
|
||||||
export const LocationSelectorTitle = localize('dataworkspace.locationSelectorTitle', "Location");
|
export const LocationSelectorTitle = localize('dataworkspace.locationSelectorTitle', "Location");
|
||||||
export const ProjectFilePlaceholder = localize('dataworkspace.projectFilePlaceholder', "Select project (.sqlproj) file");
|
export const ProjectFilePlaceholder = localize('dataworkspace.projectFilePlaceholder', "Select project (.sqlproj) file");
|
||||||
export const WorkspacePlaceholder = localize('dataworkspace.workspacePlaceholder', "Select workspace (.code-workspace) file");
|
export const WorkspacePlaceholder = localize('dataworkspace.workspacePlaceholder', "Select workspace ({0}) file", WorkspaceFileExtension);
|
||||||
export const WorkspaceFileExtension = 'code-workspace';
|
|
||||||
|
|
||||||
// Workspace settings for saving new projects
|
// Workspace settings for saving new projects
|
||||||
export const ProjectConfigurationKey = 'projects';
|
export const ProjectConfigurationKey = 'projects';
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ export interface IWorkspaceService {
|
|||||||
/**
|
/**
|
||||||
* Adds the projects to workspace, if a project is not in the workspace folder, its containing folder will be added to the workspace
|
* Adds the projects to workspace, if a project is not in the workspace folder, its containing folder will be added to the workspace
|
||||||
* @param projectFiles the list of project files to be added, the project file should be absolute path.
|
* @param projectFiles the list of project files to be added, the project file should be absolute path.
|
||||||
|
* @param workspaceFilePath The workspace file to create if a workspace isn't currently open
|
||||||
*/
|
*/
|
||||||
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void>;
|
addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the project from workspace
|
* Remove the project from workspace
|
||||||
@@ -76,8 +77,9 @@ export interface IWorkspaceService {
|
|||||||
* @param name The name of the project
|
* @param name The name of the project
|
||||||
* @param location The location of the project
|
* @param location The location of the project
|
||||||
* @param projectTypeId The project type id
|
* @param projectTypeId The project type id
|
||||||
|
* @param workspaceFile The workspace file to create if a workspace isn't currently open
|
||||||
*/
|
*/
|
||||||
createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri>;
|
createProject(name: string, location: vscode.Uri, projectTypeId: string, workspaceFile?: vscode.Uri): Promise<vscode.Uri>;
|
||||||
|
|
||||||
readonly isProjectProviderAvailable: boolean;
|
readonly isProjectProviderAvailable: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import * as azdata from 'azdata';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
|
import { IconPathHelper } from '../common/iconHelper';
|
||||||
|
import { directoryExist, fileExist } from '../common/utils';
|
||||||
|
|
||||||
interface Deferred<T> {
|
interface Deferred<T> {
|
||||||
resolve: (result: T | Promise<T>) => void;
|
resolve: (result: T | Promise<T>) => void;
|
||||||
@@ -18,8 +20,9 @@ export abstract class DialogBase {
|
|||||||
protected _dialogObject: azdata.window.Dialog;
|
protected _dialogObject: azdata.window.Dialog;
|
||||||
protected initDialogComplete: Deferred<void> | undefined;
|
protected initDialogComplete: Deferred<void> | undefined;
|
||||||
protected initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
|
protected initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
|
||||||
protected workspaceFormComponent: azdata.FormComponent | undefined;
|
protected workspaceDescriptionFormComponent: azdata.FormComponent | undefined;
|
||||||
protected workspaceInputBox: azdata.InputBoxComponent | undefined;
|
public workspaceInputBox: azdata.InputBoxComponent | undefined;
|
||||||
|
protected workspaceInputFormComponent: azdata.FormComponent | undefined;
|
||||||
|
|
||||||
constructor(dialogTitle: string, dialogName: string, dialogWidth: azdata.window.DialogWidth = 600) {
|
constructor(dialogTitle: string, dialogName: string, dialogWidth: azdata.window.DialogWidth = 600) {
|
||||||
this._dialogObject = azdata.window.createModelViewDialog(dialogTitle, dialogName, dialogWidth);
|
this._dialogObject = azdata.window.createModelViewDialog(dialogTitle, dialogName, dialogWidth);
|
||||||
@@ -83,31 +86,64 @@ export abstract class DialogBase {
|
|||||||
* created if no workspace is currently open
|
* created if no workspace is currently open
|
||||||
* @param view
|
* @param view
|
||||||
*/
|
*/
|
||||||
protected createWorkspaceContainer(view: azdata.ModelView): azdata.FormComponent {
|
protected createWorkspaceContainer(view: azdata.ModelView): void {
|
||||||
const workspaceDescription = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const workspaceDescription = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||||
value: vscode.workspace.workspaceFile ? constants.AddProjectToCurrentWorkspace : constants.NewWorkspaceWillBeCreated,
|
value: vscode.workspace.workspaceFile ? constants.AddProjectToCurrentWorkspace : constants.NewWorkspaceWillBeCreated,
|
||||||
CSSStyles: { 'margin-top': '3px', 'margin-bottom': '10px' }
|
CSSStyles: { 'margin-top': '3px', 'margin-bottom': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.workspaceInputBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
this.workspaceInputBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
ariaLabel: constants.WorkspaceLocationTitle,
|
ariaLabel: constants.WorkspaceLocationTitle,
|
||||||
width: constants.DefaultInputWidth,
|
width: constants.DefaultInputWidth,
|
||||||
enabled: false,
|
enabled: !vscode.workspace.workspaceFile, // want it editable if no workspace is open
|
||||||
value: vscode.workspace.workspaceFile?.fsPath ?? '',
|
value: vscode.workspace.workspaceFile?.fsPath ?? '',
|
||||||
title: vscode.workspace.workspaceFile?.fsPath ?? '' // hovertext for if file path is too long to be seen in textbox
|
title: vscode.workspace.workspaceFile?.fsPath ?? '' // hovertext for if file path is too long to be seen in textbox
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const container = view.modelBuilder.flexContainer()
|
const browseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
.withItems([workspaceDescription, this.workspaceInputBox])
|
ariaLabel: constants.BrowseButtonText,
|
||||||
.withLayout({ flexFlow: 'column' })
|
iconPath: IconPathHelper.folder,
|
||||||
.component();
|
height: '16px',
|
||||||
|
width: '18px'
|
||||||
|
}).component();
|
||||||
|
|
||||||
this.workspaceFormComponent = {
|
this.register(browseFolderButton.onDidClick(async () => {
|
||||||
title: constants.Workspace,
|
const currentFileName = path.parse(this.workspaceInputBox!.value!).base;
|
||||||
component: container
|
|
||||||
|
// let user select folder for workspace file to be created in
|
||||||
|
const folderUris = await vscode.window.showOpenDialog({
|
||||||
|
canSelectFiles: false,
|
||||||
|
canSelectFolders: true,
|
||||||
|
canSelectMany: false,
|
||||||
|
defaultUri: vscode.Uri.file(path.parse(this.workspaceInputBox!.value!).dir)
|
||||||
|
});
|
||||||
|
if (!folderUris || folderUris.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedFolder = folderUris[0].fsPath;
|
||||||
|
|
||||||
|
const selectedFile = path.join(selectedFolder, currentFileName);
|
||||||
|
this.workspaceInputBox!.value = selectedFile;
|
||||||
|
this.workspaceInputBox!.title = selectedFile;
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (vscode.workspace.workspaceFile) {
|
||||||
|
this.workspaceInputFormComponent = {
|
||||||
|
component: this.workspaceInputBox
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
// have browse button to help select where the workspace file should be created
|
||||||
|
const horizontalContainer = this.createHorizontalContainer(view, [this.workspaceInputBox, browseFolderButton]);
|
||||||
|
this.workspaceInputFormComponent = {
|
||||||
|
component: horizontalContainer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return this.workspaceFormComponent;
|
this.workspaceDescriptionFormComponent = {
|
||||||
|
title: constants.Workspace,
|
||||||
|
component: workspaceDescription,
|
||||||
|
required: true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,4 +158,31 @@ export abstract class DialogBase {
|
|||||||
this.workspaceInputBox!.title = fileLocation;
|
this.workspaceInputBox!.title = fileLocation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async validateNewWorkspace(sameFolderAsNewProject: boolean): Promise<boolean> {
|
||||||
|
// workspace file should end in .code-workspace
|
||||||
|
const workspaceValid = this.workspaceInputBox!.value!.endsWith(constants.WorkspaceFileExtension);
|
||||||
|
if (!workspaceValid) {
|
||||||
|
this.showErrorMessage(constants.WorkspaceFileInvalidError(this.workspaceInputBox!.value!));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the workspace file is not going to be in the same folder as the newly created project, then check that it's a valid folder
|
||||||
|
if (!sameFolderAsNewProject) {
|
||||||
|
const workspaceParentDirectoryExists = await directoryExist(path.dirname(this.workspaceInputBox!.value!));
|
||||||
|
if (!workspaceParentDirectoryExists) {
|
||||||
|
this.showErrorMessage(constants.WorkspaceParentDirectoryNotExistError(this.workspaceInputBox!.value!));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// workspace file should not be an existing workspace file
|
||||||
|
const workspaceFileExists = await fileExist(this.workspaceInputBox!.value!);
|
||||||
|
if (workspaceFileExists) {
|
||||||
|
this.showErrorMessage(constants.WorkspaceFileAlreadyExistsError(this.workspaceInputBox!.value!));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sameFolderAsNewProject = path.join(this.model.location, this.model.name) === path.dirname(this.workspaceInputBox!.value!);
|
||||||
|
if (this.workspaceInputBox!.enabled && !await this.validateNewWorkspace(sameFolderAsNewProject)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -55,7 +60,7 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
try {
|
try {
|
||||||
const validateWorkspace = await this.workspaceService.validateWorkspace();
|
const validateWorkspace = await this.workspaceService.validateWorkspace();
|
||||||
if (validateWorkspace) {
|
if (validateWorkspace) {
|
||||||
await this.workspaceService.createProject(this.model.name, vscode.Uri.file(this.model.location), this.model.projectTypeId);
|
await this.workspaceService.createProject(this.model.name, vscode.Uri.file(this.model.location), this.model.projectTypeId, vscode.Uri.file(this.workspaceInputBox!.value!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -109,7 +114,7 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
this.model.name = projectNameTextBox.value!;
|
this.model.name = projectNameTextBox.value!;
|
||||||
projectNameTextBox.updateProperty('title', projectNameTextBox.value);
|
projectNameTextBox.updateProperty('title', projectNameTextBox.value);
|
||||||
|
|
||||||
this.updateWorkspaceInputbox(this.model.location, this.model.name);
|
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const locationTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
const locationTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
@@ -122,7 +127,7 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
this.register(locationTextBox.onTextChanged(() => {
|
this.register(locationTextBox.onTextChanged(() => {
|
||||||
this.model.location = locationTextBox.value!;
|
this.model.location = locationTextBox.value!;
|
||||||
locationTextBox.updateProperty('title', locationTextBox.value);
|
locationTextBox.updateProperty('title', locationTextBox.value);
|
||||||
this.updateWorkspaceInputbox(this.model.location, this.model.name);
|
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const browseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
const browseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
@@ -145,9 +150,11 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
locationTextBox.value = selectedFolder;
|
locationTextBox.value = selectedFolder;
|
||||||
this.model.location = selectedFolder;
|
this.model.location = selectedFolder;
|
||||||
|
|
||||||
this.updateWorkspaceInputbox(this.model.location, this.model.name);
|
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.createWorkspaceContainer(view);
|
||||||
|
|
||||||
const form = view.modelBuilder.formContainer().withFormItems([
|
const form = view.modelBuilder.formContainer().withFormItems([
|
||||||
{
|
{
|
||||||
title: constants.TypeTitle,
|
title: constants.TypeTitle,
|
||||||
@@ -163,7 +170,8 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
required: true,
|
required: true,
|
||||||
component: this.createHorizontalContainer(view, [locationTextBox, browseFolderButton])
|
component: this.createHorizontalContainer(view, [locationTextBox, browseFolderButton])
|
||||||
},
|
},
|
||||||
this.createWorkspaceContainer(view)
|
this.workspaceDescriptionFormComponent!,
|
||||||
|
this.workspaceInputFormComponent!
|
||||||
]).component();
|
]).component();
|
||||||
await view.initializeModel(form);
|
await view.initializeModel(form);
|
||||||
this.initDialogComplete?.resolve();
|
this.initDialogComplete?.resolve();
|
||||||
|
|||||||
@@ -39,13 +39,17 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
||||||
const fileExists = await fileExist(this._projectFile);
|
const fileExists = await fileExist(this._projectFile);
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
this.showErrorMessage(constants.ProjectFileNotExistError(this._projectFile));
|
this.showErrorMessage(constants.FileNotExistError(constants.Project.toLowerCase(), this._projectFile));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.workspaceInputBox!.enabled && !await this.validateNewWorkspace(false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
} else if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
||||||
const fileExists = await fileExist(this._workspaceFile);
|
const fileExists = await fileExist(this._workspaceFile);
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
this.showErrorMessage(constants.WorkspaceFileNotExistError(this._workspaceFile));
|
this.showErrorMessage(constants.FileNotExistError(constants.Workspace.toLowerCase(), this._workspaceFile));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +69,7 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
} else {
|
} else {
|
||||||
const validateWorkspace = await this.workspaceService.validateWorkspace();
|
const validateWorkspace = await this.workspaceService.validateWorkspace();
|
||||||
if (validateWorkspace) {
|
if (validateWorkspace) {
|
||||||
await this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this._projectFile)]);
|
await this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this._projectFile)], vscode.Uri.file(this.workspaceInputBox!.value!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,16 +134,20 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
this.register(this._targetTypeRadioCardGroup.onSelectionChanged(({ cardId }) => {
|
this.register(this._targetTypeRadioCardGroup.onSelectionChanged(({ cardId }) => {
|
||||||
if (cardId === constants.Project) {
|
if (cardId === constants.Project) {
|
||||||
this._filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder;
|
this._filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder;
|
||||||
this.formBuilder?.addFormItem(this.workspaceFormComponent!);
|
this.formBuilder?.addFormItem(this.workspaceDescriptionFormComponent!);
|
||||||
|
this.formBuilder?.addFormItem(this.workspaceInputFormComponent!);
|
||||||
} else if (cardId === constants.Workspace) {
|
} else if (cardId === constants.Workspace) {
|
||||||
this._filePathTextBox!.placeHolder = constants.WorkspacePlaceholder;
|
this._filePathTextBox!.placeHolder = constants.WorkspacePlaceholder;
|
||||||
this.formBuilder?.removeFormItem(this.workspaceFormComponent!);
|
this.formBuilder?.removeFormItem(this.workspaceDescriptionFormComponent!);
|
||||||
|
this.formBuilder?.removeFormItem(this.workspaceInputFormComponent!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear selected file textbox
|
// clear selected file textbox
|
||||||
this._filePathTextBox!.value = '';
|
this._filePathTextBox!.value = '';
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.createWorkspaceContainer(view);
|
||||||
|
|
||||||
this.formBuilder = view.modelBuilder.formContainer().withFormItems([
|
this.formBuilder = view.modelBuilder.formContainer().withFormItems([
|
||||||
{
|
{
|
||||||
title: constants.TypeTitle,
|
title: constants.TypeTitle,
|
||||||
@@ -150,14 +158,15 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
required: true,
|
required: true,
|
||||||
component: this.createHorizontalContainer(view, [this._filePathTextBox, browseFolderButton])
|
component: this.createHorizontalContainer(view, [this._filePathTextBox, browseFolderButton])
|
||||||
},
|
},
|
||||||
this.createWorkspaceContainer(view)
|
this.workspaceDescriptionFormComponent!,
|
||||||
|
this.workspaceInputFormComponent!
|
||||||
]);
|
]);
|
||||||
await view.initializeModel(this.formBuilder?.component());
|
await view.initializeModel(this.formBuilder?.component());
|
||||||
this.initDialogComplete?.resolve();
|
this.initDialogComplete?.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async workspaceBrowse(): Promise<void> {
|
public async workspaceBrowse(): Promise<void> {
|
||||||
const filters: { [name: string]: string[] } = { [constants.Workspace]: [constants.WorkspaceFileExtension] };
|
const filters: { [name: string]: string[] } = { [constants.Workspace]: [constants.WorkspaceFileExtension.substring(1)] }; // filter already adds a period before the extension
|
||||||
const fileUris = await vscode.window.showOpenDialog({
|
const fileUris = await vscode.window.showOpenDialog({
|
||||||
canSelectFiles: true,
|
canSelectFiles: true,
|
||||||
canSelectFolders: false,
|
canSelectFolders: false,
|
||||||
|
|||||||
@@ -40,17 +40,16 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new workspace in the same folder as the project. Because the extension host gets restared when
|
* Creates a new workspace in the same folder as the project. Because the extension host gets restarted when
|
||||||
* a new workspace is created and opened, the project needs to be saved as the temp project that will be loaded
|
* a new workspace is created and opened, the project needs to be saved as the temp project that will be loaded
|
||||||
* when the extension gets restarted
|
* when the extension gets restarted
|
||||||
* @param projectFileFsPath project to add to the workspace
|
* @param projectFileFsPath project to add to the workspace
|
||||||
*/
|
*/
|
||||||
async CreateNewWorkspaceForProject(projectFileFsPath: string): Promise<void> {
|
async CreateNewWorkspaceForProject(projectFileFsPath: string, workspaceFile: vscode.Uri | undefined): Promise<void> {
|
||||||
// save temp project
|
// save temp project
|
||||||
await this._context.globalState.update(TempProject, [projectFileFsPath]);
|
await this._context.globalState.update(TempProject, [projectFileFsPath]);
|
||||||
|
|
||||||
// create a new workspace - the workspace file will be created in the same folder as the project
|
// create a new workspace
|
||||||
const workspaceFile = vscode.Uri.file(path.join(path.dirname(projectFileFsPath), `${path.parse(projectFileFsPath).name}.code-workspace`));
|
|
||||||
const projectFolder = vscode.Uri.file(path.dirname(projectFileFsPath));
|
const projectFolder = vscode.Uri.file(path.dirname(projectFileFsPath));
|
||||||
await azdata.workspace.createWorkspace(projectFolder, workspaceFile);
|
await azdata.workspace.createWorkspace(projectFolder, workspaceFile);
|
||||||
}
|
}
|
||||||
@@ -95,14 +94,14 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void> {
|
async addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void> {
|
||||||
if (!projectFiles || projectFiles.length === 0) {
|
if (!projectFiles || projectFiles.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a workspace needs to be open to add projects
|
// a workspace needs to be open to add projects
|
||||||
if (!vscode.workspace.workspaceFile) {
|
if (!vscode.workspace.workspaceFile) {
|
||||||
await this.CreateNewWorkspaceForProject(projectFiles[0].fsPath);
|
await this.CreateNewWorkspaceForProject(projectFiles[0].fsPath, workspaceFilePath);
|
||||||
|
|
||||||
// this won't get hit since the extension host will get restarted, but helps with testing
|
// this won't get hit since the extension host will get restarted, but helps with testing
|
||||||
return;
|
return;
|
||||||
@@ -171,11 +170,11 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri> {
|
async createProject(name: string, location: vscode.Uri, projectTypeId: string, workspaceFile?: vscode.Uri): Promise<vscode.Uri> {
|
||||||
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
|
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
|
||||||
if (provider) {
|
if (provider) {
|
||||||
const projectFile = await provider.createProject(name, location, projectTypeId);
|
const projectFile = await provider.createProject(name, location, projectTypeId);
|
||||||
this.addProjectsToWorkspace([projectFile]);
|
this.addProjectsToWorkspace([projectFile], workspaceFile);
|
||||||
this._onDidWorkspaceProjectsChange.fire();
|
this._onDidWorkspaceProjectsChange.fire();
|
||||||
return projectFile;
|
return projectFile;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ suite('New Project Dialog', function (): void {
|
|||||||
|
|
||||||
dialog.model.name = 'TestProject';
|
dialog.model.name = 'TestProject';
|
||||||
dialog.model.location = '';
|
dialog.model.location = '';
|
||||||
should.equal(await dialog.validate(), false, 'Validation should fail becausee the parent directory does not exist');
|
dialog.workspaceInputBox!.value = 'test.code-workspace';
|
||||||
|
should.equal(await dialog.validate(), false, 'Validation should fail because the parent directory does not exist');
|
||||||
|
|
||||||
// create a folder with the same name
|
// create a folder with the same name
|
||||||
const folderPath = path.join(os.tmpdir(), dialog.model.name);
|
const folderPath = path.join(os.tmpdir(), dialog.model.name);
|
||||||
@@ -37,6 +38,37 @@ suite('New Project Dialog', function (): void {
|
|||||||
should.equal(await dialog.validate(), true, 'Validation should pass because name is unique and parent directory exists');
|
should.equal(await dialog.validate(), true, 'Validation should pass because name is unique and parent directory exists');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should validate new workspace location', async function (): Promise<void> {
|
||||||
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
|
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([testProjectType]));
|
||||||
|
|
||||||
|
const dialog = new NewProjectDialog(workspaceServiceMock.object);
|
||||||
|
await dialog.open();
|
||||||
|
|
||||||
|
dialog.model.name = `TestProject_${new Date().getTime()}`;
|
||||||
|
dialog.model.location = os.tmpdir();
|
||||||
|
dialog.workspaceInputBox!.value = 'test';
|
||||||
|
should.equal(await dialog.validate(), false, 'Validation should fail because workspace does not end in .code-workspace');
|
||||||
|
|
||||||
|
// use invalid folder
|
||||||
|
dialog.workspaceInputBox!.value = 'invalidLocation/test.code-workspace';
|
||||||
|
should.equal(await dialog.validate(), false, 'Validation should fail because the folder is invalid');
|
||||||
|
|
||||||
|
// use already existing workspace
|
||||||
|
const existingWorkspaceFilePath = path.join(os.tmpdir(), `${dialog.model.name}.code-workspace`);
|
||||||
|
await fs.writeFile(existingWorkspaceFilePath, '');
|
||||||
|
dialog.workspaceInputBox!.value = existingWorkspaceFilePath;
|
||||||
|
should.equal(await dialog.validate(), false, 'Validation should fail because the selected workspace file already exists');
|
||||||
|
|
||||||
|
// same folder as the project should be valid even if the project folder isn't created yet
|
||||||
|
dialog.workspaceInputBox!.value = path.join(dialog.model.location, dialog.model.name, 'test.code-workspace');
|
||||||
|
should.equal(await dialog.validate(), true, 'Validation should pass if the file location is the same folder as the project');
|
||||||
|
|
||||||
|
// change workspace name to something that should pass
|
||||||
|
dialog.workspaceInputBox!.value = path.join(os.tmpdir(), `TestWorkspace_${new Date().getTime()}.code-workspace`);
|
||||||
|
should.equal(await dialog.validate(), true, 'Validation should pass because the parent directory exists, workspace filepath is unique, and the file extension is correct');
|
||||||
|
});
|
||||||
|
|
||||||
test('Should validate workspace in onComplete', async function (): Promise<void> {
|
test('Should validate workspace in onComplete', async function (): Promise<void> {
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
workspaceServiceMock.setup(x => x.validateWorkspace()).returns(() => Promise.resolve(true));
|
workspaceServiceMock.setup(x => x.validateWorkspace()).returns(() => Promise.resolve(true));
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import * as should from 'should';
|
|||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
import * as constants from '../../common/constants';
|
import * as constants from '../../common/constants';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { WorkspaceService } from '../../services/workspaceService';
|
import { WorkspaceService } from '../../services/workspaceService';
|
||||||
@@ -27,6 +29,8 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
|
|
||||||
dialog._targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Project);
|
dialog._targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Project);
|
||||||
dialog._projectFile = '';
|
dialog._projectFile = '';
|
||||||
|
dialog.workspaceInputBox!.value = 'test.code-workspace';
|
||||||
|
|
||||||
should.equal(await dialog.validate(), false, 'Validation fail because project file does not exist');
|
should.equal(await dialog.validate(), false, 'Validation fail because project file does not exist');
|
||||||
|
|
||||||
// create a project file
|
// create a project file
|
||||||
@@ -49,6 +53,32 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
should.equal(await dialog.validate(), true, 'Validation pass because workspace file exists');
|
should.equal(await dialog.validate(), true, 'Validation pass because workspace file exists');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should validate new workspace location', async function (): Promise<void> {
|
||||||
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
|
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([testProjectType]));
|
||||||
|
|
||||||
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
||||||
|
await dialog.open();
|
||||||
|
|
||||||
|
dialog._projectFile = await createProjectFile('testproj');
|
||||||
|
dialog.workspaceInputBox!.value = 'test';
|
||||||
|
should.equal(await dialog.validate(), false, 'Validation should fail because workspace does not end in code-workspace');
|
||||||
|
|
||||||
|
// use invalid folder
|
||||||
|
dialog.workspaceInputBox!.value = 'invalidLocation/test.code-workspace';
|
||||||
|
should.equal(await dialog.validate(), false, 'Validation should fail because the folder is invalid');
|
||||||
|
|
||||||
|
// use already existing workspace
|
||||||
|
const existingWorkspaceFilePath = path.join(os.tmpdir(), `test.code-workspace`);
|
||||||
|
await fs.writeFile(existingWorkspaceFilePath, '');
|
||||||
|
dialog.workspaceInputBox!.value = existingWorkspaceFilePath;
|
||||||
|
should.equal(await dialog.validate(), false, 'Validation should fail because the selected workspace file already exists');
|
||||||
|
|
||||||
|
// change workspace name to something that should pass
|
||||||
|
dialog.workspaceInputBox!.value = path.join(os.tmpdir(), `TestWorkspace_${new Date().getTime()}.code-workspace`);
|
||||||
|
should.equal(await dialog.validate(), true, 'Validation should pass because the parent directory exists, workspace filepath is unique, and the file extension is correct');
|
||||||
|
});
|
||||||
|
|
||||||
test('Should validate workspace in onComplete when opening project', async function (): Promise<void> {
|
test('Should validate workspace in onComplete when opening project', async function (): Promise<void> {
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
workspaceServiceMock.setup(x => x.validateWorkspace()).returns(() => Promise.resolve(true));
|
workspaceServiceMock.setup(x => x.validateWorkspace()).returns(() => Promise.resolve(true));
|
||||||
|
|||||||
Reference in New Issue
Block a user