mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 17:22:48 -05:00
.sqlproj and datasources.json file parsing (#8921)
* Checkpoint * Adding mock contents for tree * added open sqlproj dialog * reading files from directory * Added directory traversal * Adding tree sorting by folder vs file and label * Improved auto-unfolding of tree based on node type * replacing fs with fs.promise alias * PR feedback * added activation event for when workspace contains sqlproj files * Returning after displaying error * Fixing linter errors * Reworked tree * Fixing missing grandchildren * Correcting tree URI construction * Refactoring to isolate tree item responsibilities from data model responsibilities * project file parsing * constructing tree from project files rather than filesystem * Fixing double-initialization * Changing projectEntry to take enum for file type * Correct node type for project item * Parsing datasources.json * Child nodes for sql data source * Localizing strings * Checkpoint * Adding mock contents for tree * added open sqlproj dialog * reading files from directory * Added directory traversal * Adding tree sorting by folder vs file and label * Improved auto-unfolding of tree based on node type * replacing fs with fs.promise alias * PR feedback * added activation event for when workspace contains sqlproj files * Returning after displaying error * Fixing linter errors * Reworked tree * Fixing missing grandchildren * Correcting tree URI construction * Refactoring to isolate tree item responsibilities from data model responsibilities * project file parsing * constructing tree from project files rather than filesystem * Fixing double-initialization * Changing projectEntry to take enum for file type * Correct node type for project item * Parsing datasources.json * Child nodes for sql data source * Localizing strings * missed file in merge * changed extension method to helper * cleanup * Adding docstrings
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export class SqlDatabaseProjectItem {
|
||||
label: string;
|
||||
readonly isFolder: boolean;
|
||||
readonly parent?: SqlDatabaseProjectItem;
|
||||
children: SqlDatabaseProjectItem[] = [];
|
||||
|
||||
constructor(label: string, isFolder: boolean, parent?: SqlDatabaseProjectItem) {
|
||||
this.label = label;
|
||||
this.isFolder = isFolder;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public createChild(label: string, isFolder: boolean): SqlDatabaseProjectItem {
|
||||
let child = new SqlDatabaseProjectItem(label, isFolder, this);
|
||||
this.children.push(child);
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -4,38 +4,34 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as constants from '../common/constants';
|
||||
|
||||
import { SqlDatabaseProjectItem } from './databaseProjectTreeItem';
|
||||
import { BaseProjectTreeItem, MessageTreeItem } from '../models/tree/baseTreeItem';
|
||||
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
|
||||
import { Project } from '../models/project';
|
||||
|
||||
export class SqlDatabaseProjectTreeViewProvider implements vscode.TreeDataProvider<SqlDatabaseProjectItem> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<SqlDatabaseProjectItem | undefined> = new vscode.EventEmitter<SqlDatabaseProjectItem | undefined>();
|
||||
readonly onDidChangeTreeData: vscode.Event<SqlDatabaseProjectItem | undefined> = this._onDidChangeTreeData.event;
|
||||
/**
|
||||
* Tree view for database projects
|
||||
*/
|
||||
export class SqlDatabaseProjectTreeViewProvider implements vscode.TreeDataProvider<BaseProjectTreeItem> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<BaseProjectTreeItem | undefined> = new vscode.EventEmitter<BaseProjectTreeItem | undefined>();
|
||||
readonly onDidChangeTreeData: vscode.Event<BaseProjectTreeItem | undefined> = this._onDidChangeTreeData.event;
|
||||
|
||||
private roots: SqlDatabaseProjectItem[] = [];
|
||||
private roots: BaseProjectTreeItem[] = [];
|
||||
|
||||
constructor() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private initialize() {
|
||||
this.roots = [new SqlDatabaseProjectItem(constants.noOpenProjectMessage, false)];
|
||||
this.roots = [new MessageTreeItem(constants.noOpenProjectMessage)];
|
||||
}
|
||||
|
||||
public getTreeItem(element: SqlDatabaseProjectItem): vscode.TreeItem {
|
||||
return {
|
||||
label: element.label,
|
||||
collapsibleState: element.parent === undefined
|
||||
? vscode.TreeItemCollapsibleState.Expanded
|
||||
: element.isFolder
|
||||
? vscode.TreeItemCollapsibleState.Collapsed
|
||||
: vscode.TreeItemCollapsibleState.None
|
||||
};
|
||||
public getTreeItem(element: BaseProjectTreeItem): vscode.TreeItem {
|
||||
return element.treeItem;
|
||||
}
|
||||
|
||||
public getChildren(element?: SqlDatabaseProjectItem): SqlDatabaseProjectItem[] {
|
||||
public getChildren(element?: BaseProjectTreeItem): BaseProjectTreeItem[] {
|
||||
if (element === undefined) {
|
||||
return this.roots;
|
||||
}
|
||||
@@ -43,78 +39,19 @@ export class SqlDatabaseProjectTreeViewProvider implements vscode.TreeDataProvid
|
||||
return element.children;
|
||||
}
|
||||
|
||||
public async openProject(projectFiles: vscode.Uri[]) {
|
||||
if (projectFiles.length > 1) { // TODO: how to handle opening a folder with multiple .sqlproj files?
|
||||
vscode.window.showErrorMessage(constants.multipleSqlProjFiles);
|
||||
return;
|
||||
}
|
||||
|
||||
if (projectFiles.length === 0) {
|
||||
public load(projects: Project[]) {
|
||||
if (projects.length === 0) {
|
||||
vscode.window.showErrorMessage(constants.noSqlProjFiles);
|
||||
return;
|
||||
}
|
||||
|
||||
let directoryPath = path.dirname(projectFiles[0].fsPath);
|
||||
console.log('Opening project directory: ' + directoryPath);
|
||||
let newRoots: BaseProjectTreeItem[] = [];
|
||||
|
||||
let newRoots: SqlDatabaseProjectItem[] = [];
|
||||
|
||||
newRoots.push(await this.constructDataSourcesTree(directoryPath));
|
||||
newRoots.push(await this.constructProjectTree(directoryPath));
|
||||
for (const proj of projects) {
|
||||
newRoots.push(new ProjectRootTreeItem(proj));
|
||||
}
|
||||
|
||||
this.roots = newRoots;
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
private async constructProjectTree(directoryPath: string): Promise<SqlDatabaseProjectItem> {
|
||||
let projectsNode = await this.constructFileTreeNode(directoryPath, undefined);
|
||||
|
||||
projectsNode.label = constants.projectNodeName;
|
||||
|
||||
return projectsNode;
|
||||
}
|
||||
|
||||
private async constructFileTreeNode(entryPath: string, parentNode: SqlDatabaseProjectItem | undefined): Promise<SqlDatabaseProjectItem> {
|
||||
let stat = await fs.stat(entryPath);
|
||||
|
||||
let output = parentNode === undefined
|
||||
? new SqlDatabaseProjectItem(path.basename(entryPath), stat.isDirectory())
|
||||
: parentNode.createChild(path.basename(entryPath), stat.isDirectory());
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
let contents = await fs.readdir(entryPath);
|
||||
|
||||
for (const entry of contents) {
|
||||
await this.constructFileTreeNode(path.join(entryPath, entry), output);
|
||||
}
|
||||
|
||||
// sort children so that folders come first, then alphabetical
|
||||
output.children.sort((a: SqlDatabaseProjectItem, b: SqlDatabaseProjectItem) => {
|
||||
if (a.isFolder && !b.isFolder) { return -1; }
|
||||
else if (!a.isFolder && b.isFolder) { return 1; }
|
||||
else { return a.label.localeCompare(b.label); }
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private async constructDataSourcesTree(directoryPath: string): Promise<SqlDatabaseProjectItem> {
|
||||
let dataSourceNode = new SqlDatabaseProjectItem(constants.dataSourcesNodeName, true);
|
||||
|
||||
let dataSourcesFilePath = path.join(directoryPath, constants.dataSourcesFileName);
|
||||
|
||||
try {
|
||||
let connections = await fs.readFile(dataSourcesFilePath, 'r');
|
||||
|
||||
// TODO: parse connections.json
|
||||
|
||||
dataSourceNode.createChild(constants.foundDataSourcesFile + connections.length, false);
|
||||
}
|
||||
catch {
|
||||
dataSourceNode.createChild(constants.noDataSourcesFile, false);
|
||||
}
|
||||
|
||||
return dataSourceNode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as nls from 'vscode-nls';
|
||||
|
||||
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
|
||||
import { getErrorMessage } from '../common/utils';
|
||||
import { ProjectsController } from './projectController';
|
||||
|
||||
const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView';
|
||||
|
||||
@@ -19,9 +20,11 @@ const localize = nls.loadMessageBundle();
|
||||
export default class MainController implements vscode.Disposable {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
protected dbProjectTreeViewProvider: SqlDatabaseProjectTreeViewProvider = new SqlDatabaseProjectTreeViewProvider();
|
||||
protected projectsController: ProjectsController;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
this.projectsController = new ProjectsController(this.dbProjectTreeViewProvider);
|
||||
}
|
||||
|
||||
public get extensionContext(): vscode.ExtensionContext {
|
||||
@@ -38,24 +41,28 @@ export default class MainController implements vscode.Disposable {
|
||||
private async initializeDatabaseProjects(): Promise<void> {
|
||||
// init commands
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.new', () => { console.log('"New Database Project" called.'); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.open', async () => { this.openProjectFolder(); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.open', async () => { this.openProjectFromFile(); });
|
||||
|
||||
// init view
|
||||
this.dbProjectTreeViewProvider = new SqlDatabaseProjectTreeViewProvider();
|
||||
|
||||
this.extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(SQL_DATABASE_PROJECTS_VIEW_ID, this.dbProjectTreeViewProvider));
|
||||
}
|
||||
|
||||
public async openProjectFolder(): Promise<void> {
|
||||
/**
|
||||
* Prompts the user to select a .sqlproj file to open
|
||||
* TODO: define behavior once projects are automatically opened from workspace
|
||||
*/
|
||||
public async openProjectFromFile(): Promise<void> {
|
||||
try {
|
||||
let filter: { [key: string]: string[] } = {};
|
||||
|
||||
filter[localize('sqlDatabaseProject', "SQL database project")] = ['sqlproj'];
|
||||
|
||||
let file = await vscode.window.showOpenDialog({ filters: filter });
|
||||
let files: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ filters: filter });
|
||||
|
||||
if (file) {
|
||||
await this.dbProjectTreeViewProvider.openProject(file);
|
||||
if (files) {
|
||||
for (const file of files) {
|
||||
await this.projectsController.openProject(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Project } from '../models/project';
|
||||
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
|
||||
import * as path from 'path';
|
||||
import * as constants from '../common/constants';
|
||||
import * as dataSources from '../models/dataSources/dataSources';
|
||||
|
||||
/**
|
||||
* Controller for managing project lifecycle
|
||||
*/
|
||||
export class ProjectsController {
|
||||
private projectTreeViewProvider: SqlDatabaseProjectTreeViewProvider;
|
||||
|
||||
projects: Project[] = [];
|
||||
|
||||
constructor(projTreeViewProvider: SqlDatabaseProjectTreeViewProvider) {
|
||||
this.projectTreeViewProvider = projTreeViewProvider;
|
||||
}
|
||||
|
||||
public async openProject(projectFile: vscode.Uri) {
|
||||
console.log('Loading project: ' + projectFile.fsPath);
|
||||
|
||||
// Read project file
|
||||
const newProject = new Project(projectFile.fsPath);
|
||||
await newProject.readProjFile();
|
||||
this.projects.push(newProject);
|
||||
|
||||
// Read datasources.json (if present)
|
||||
const dataSourcesFilePath = path.join(path.dirname(projectFile.fsPath), constants.dataSourcesFileName);
|
||||
newProject.dataSources = await dataSources.load(dataSourcesFilePath);
|
||||
|
||||
this.refreshProjectsTree();
|
||||
}
|
||||
|
||||
public refreshProjectsTree() {
|
||||
this.projectTreeViewProvider.load(this.projects);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user