diff --git a/package.json b/package.json index 2e84393..4e8089c 100644 --- a/package.json +++ b/package.json @@ -326,6 +326,16 @@ "command": "intersystems-community.servermanager.viewNamespaceWebAppFiles", "title": "View Web Application Files", "icon": "$(telescope)" + }, + { + "command": "intersystems-community.servermanager.editProject", + "title": "Edit Code in Project", + "icon": "$(edit)" + }, + { + "command": "intersystems-community.servermanager.viewProject", + "title": "View Code in Project", + "icon": "$(eye)" } ], "submenus": [ @@ -433,6 +443,14 @@ { "command": "intersystems-community.servermanager.viewNamespaceWebAppFiles", "when": "false" + }, + { + "command": "intersystems-community.servermanager.editProject", + "when": "false" + }, + { + "command": "intersystems-community.servermanager.viewProject", + "when": "false" } ], "view/title": [ @@ -490,6 +508,16 @@ "when": "view == intersystems-community_servermanager && viewItem =~ /namespace$/", "group": "inline@20" }, + { + "command": "intersystems-community.servermanager.editProject", + "when": "view == intersystems-community_servermanager && viewItem == project", + "group": "inline@10" + }, + { + "command": "intersystems-community.servermanager.viewProject", + "when": "view == intersystems-community_servermanager && viewItem == project", + "group": "inline@20" + }, { "command": "intersystems-community.servermanager.openPortalTab", "when": "view == intersystems-community_servermanager && viewItem =~ /\\.server\\./", diff --git a/src/extension.ts b/src/extension.ts index d87b3c5..4a69f2b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,7 +7,7 @@ import { getServerNames } from './api/getServerNames'; import { getServerSpec } from './api/getServerSpec'; import { storePassword, clearPassword } from './commands/managePasswords'; import { importFromRegistry } from './commands/importFromRegistry'; -import { ServerManagerView, ServerTreeItem, SMTreeItem } from './ui/serverManagerView'; +import { NamespaceTreeItem, ProjectTreeItem, ServerManagerView, ServerTreeItem, SMTreeItem } from './ui/serverManagerView'; import { addServer } from './api/addServer'; import { getServerSummary } from './api/getServerSummary'; import { BrowserTarget, getPortalUriWithToken } from './api/getPortalUriWithToken'; @@ -204,7 +204,7 @@ export function activate(context: vscode.ExtensionContext) { }) ); - const addWorkspaceFolderAsync = async (readonly: boolean, csp: boolean, namespaceTreeItem?: ServerTreeItem) => { + const addWorkspaceFolderAsync = async (readonly: boolean, csp: boolean, namespaceTreeItem?: ServerTreeItem, project?: string) => { if (namespaceTreeItem) { const pathParts = namespaceTreeItem.id?.split(':'); if (pathParts && pathParts.length === 4) { @@ -228,9 +228,10 @@ export function activate(context: vscode.ExtensionContext) { return; } - const uri = vscode.Uri.parse(`isfs${readonly ? "-readonly" : ""}://${serverName}:${namespace}/${csp ? '?csp' : ''}`); + const params = [ csp ? "csp" : "", project ? `project=${project}` : ""].filter(e => e != "").join("&"); + const uri = vscode.Uri.parse(`isfs${readonly ? "-readonly" : ""}://${serverName}:${namespace}/${params ? `?${params}` : ""}`); if ((vscode.workspace.workspaceFolders || []).filter((workspaceFolder) => workspaceFolder.uri.toString() === uri.toString()).length === 0) { - const label = `${serverName}:${namespace}${csp ? ' webfiles' : ''}${readonly ? " (read-only)" : ""}`; + const label = `${project ? `${project} - ` : ""}${serverName}:${namespace}${csp ? ' web files' : ''}${readonly && project == undefined ? " (read-only)" : ""}`; const added = vscode.workspace.updateWorkspaceFolders( vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders.length : 0, 0, @@ -267,6 +268,18 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand(`${extensionId}.viewNamespaceWebAppFiles`, async (namespaceTreeItem?: ServerTreeItem) => {await addWorkspaceFolderAsync(true, true, namespaceTreeItem)}) ); + context.subscriptions.push( + vscode.commands.registerCommand(`${extensionId}.editProject`, async (projectTreeItem?: ProjectTreeItem) => { + await addWorkspaceFolderAsync(false, false, projectTreeItem?.parent?.parent, projectTreeItem?.name); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand(`${extensionId}.viewProject`, async (projectTreeItem?: ProjectTreeItem) => { + await addWorkspaceFolderAsync(true, false, projectTreeItem?.parent?.parent, projectTreeItem?.name); + }) + ); + // Listen for relevant configuration changes context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('intersystems.servers') || e.affectsConfiguration('objectscript.conn')) { diff --git a/src/ui/serverManagerView.ts b/src/ui/serverManagerView.ts index 704245e..966fb80 100644 --- a/src/ui/serverManagerView.ts +++ b/src/ui/serverManagerView.ts @@ -427,10 +427,98 @@ export class NamespaceTreeItem extends SMTreeItem { parent: element.parent, label: name, id, - tooltip: `${name} on ${serverName}` + tooltip: `${name} on ${serverName}`, + getChildren: namespaceFeatures, + params: { serverName } }); this.name = name; this.contextValue = name === '%SYS' ? 'sysnamespace' : 'namespace'; this.iconPath = new vscode.ThemeIcon('archive'); } } + +/** + * getChildren function returning namespace features (the child nodes of a server), + * + * @param element parent + * @param params (unused) + * @returns feature folders of a namespace. + */ + async function namespaceFeatures(element: NamespaceTreeItem, params?: any): Promise { + return [new ProjectsTreeItem({ parent: element, id: element.name, label: element.name }, params.serverName)]; +} + +export class ProjectsTreeItem extends FeatureTreeItem { + public readonly name: string; + constructor( + element: SMItem, + serverName: string + ) { + const parentFolderId = element.parent?.id || ''; + super({ + parent: element.parent, + label: 'Projects', + id: parentFolderId + ':projects', + tooltip: `Projects in this namespace`, + getChildren: namespaceProjects, + params: { serverName, ns: element.label } + }); + this.name = 'Projects'; + this.contextValue = 'projects'; + this.iconPath = new vscode.ThemeIcon('library'); + } +} + +/** + * getChildren function returning projects in a server namespace. + * + * @param element parent + * @param params { serverName } + * @returns projects in a server namespace. + */ +async function namespaceProjects(element: ProjectsTreeItem, params?: any): Promise { + const children: ProjectTreeItem[] = []; + + if (params?.serverName && params.ns) { + const name: string = params.serverName; + const serverSpec = await getServerSpec(name) + if (!serverSpec) { + return undefined + } + + const response = await makeRESTRequest( + "POST", + serverSpec, + { apiVersion: 1, namespace: params.ns, path: "/action/query" }, + { query: "SELECT Name, Description FROM %Studio.Project", parameters: [] } + ); + if (response !== undefined) { + response.data.result.content.map((project) => { + children.push(new ProjectTreeItem({ parent: element, label: name, id: name }, project.Name, project.Description)); + }); + } + } + + return children; +} + +export class ProjectTreeItem extends SMTreeItem { + public readonly name: string; + constructor( + element: SMItem, + name: string, + description: string + ) { + const parentFolderId = element.parent?.id || ''; + const id = parentFolderId + ':' + name; + super({ + parent: element.parent, + label: name, + id, + tooltip: description + }); + this.name = name; + this.contextValue = 'project'; + this.iconPath = new vscode.ThemeIcon('files'); + } +}