diff --git a/src/controller/diffController.ts b/src/controller/diffController.ts index a7491ae1..7295954d 100644 --- a/src/controller/diffController.ts +++ b/src/controller/diffController.ts @@ -1,17 +1,29 @@ import { spawn } from "child_process"; import * as vscode from "vscode"; +import { AddonTreeItem } from "../model/sidebarTreeDataProvider"; import { DiffView } from "../views/diffView"; export class DiffController { constructor(private config: vscode.WorkspaceConfiguration) {} + /** + * Open an addon for view from a TreeView. + * @param _ The specific AddonTreeItem the user opened the context menu on. + * @param list Selected AddonTreeItems + * @returns Whether the diff tool successfully launched. + */ + async diffFromSidebar(_: unknown, list: AddonTreeItem[]) { + const [first, second] = list; + return this.openInDiffTool([first.uri, second.uri]); + } + /** * Launches the external diff tool. * @param uris The files to compare. * @returns Whether the diff tool successfully launched. */ - async openInDiffTool(uris: [vscode.Uri, vscode.Uri]) { + private async openInDiffTool(uris: [vscode.Uri, vscode.Uri]) { const [left, right] = uris; const leftUri = vscode.Uri.parse(left.toString()); const rightUri = vscode.Uri.parse(right.toString()); diff --git a/src/controller/sidebarController.ts b/src/controller/sidebarController.ts new file mode 100644 index 00000000..886f8766 --- /dev/null +++ b/src/controller/sidebarController.ts @@ -0,0 +1,28 @@ +import * as vscode from "vscode"; + +import { DirectoryController } from "./directoryController"; +import { AddonTreeDataProvider } from "../model/sidebarTreeDataProvider"; + +export class SidebarController { + constructor( + public id: string, + private directoryController: DirectoryController + ) {} + + /** + * Fetch the TreeView of addons. + * @returns TreeView and refresh method. + */ + async getTreeView() { + const rootFolderPath = await this.directoryController.getRootFolderPath(); + const treeProvider = new AddonTreeDataProvider(rootFolderPath); + const refresh = () => { + treeProvider.refresh(); + }; + const treeView = vscode.window.createTreeView(this.id, { + treeDataProvider: treeProvider, + canSelectMany: true, + }); + return { refresh, treeView }; + } +} diff --git a/src/controller/urlController.ts b/src/controller/urlController.ts index dc3d04d1..52af946c 100644 --- a/src/controller/urlController.ts +++ b/src/controller/urlController.ts @@ -4,6 +4,7 @@ import * as vscode from "vscode"; import { AddonController } from "./addonController"; import { DirectoryController } from "./directoryController"; import { RangeHelper } from "../helper/rangeHelper"; +import { AddonTreeItem } from "../model/sidebarTreeDataProvider"; export class UrlController implements vscode.UriHandler { constructor( @@ -12,6 +13,17 @@ export class UrlController implements vscode.UriHandler { private directoryController: DirectoryController ) {} + /** + * Open an addon for view from a TreeView. + * @param item the user-chosen add-on. + */ + async viewAddon(item: AddonTreeItem) { + const { versionPath } = await this.directoryController.splitUri(item.uri); + if (versionPath) { + this.openWorkspace(versionPath); + } + } + /** * Given a file and line(s), focuses VS Code onto the file and line(s). * @param uri The URI of the file. diff --git a/src/extension.ts b/src/extension.ts index 9afe823d..da6397ab 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,13 +9,12 @@ import { DiffController } from "./controller/diffController"; import { DirectoryController } from "./controller/directoryController"; import { FileDecoratorController } from "./controller/fileDecoratorController"; import { LintController } from "./controller/lintController"; +import { SidebarController } from "./controller/sidebarController"; import { StatusBarController } from "./controller/statusBarController"; import { UrlController } from "./controller/urlController"; import { UpdateHelper } from "./helper/updateHelper"; import { AssayCache } from "./model/assayCache"; import { CustomFileDecorationProvider } from "./model/fileDecorationProvider"; -import { AssayTreeDataProvider } from "./views/sidebarView"; -import { SidebarTreeDataProvider } from "./views/test"; import { WelcomeView } from "./views/welcomeView"; export async function activate(context: vscode.ExtensionContext) { @@ -43,9 +42,29 @@ export async function activate(context: vscode.ExtensionContext) { const UriHandlerDisposable = vscode.window.registerUriHandler(urlController); - const sidebarDisposable = vscode.window.createTreeView("assayCommands", { - treeDataProvider: new SidebarTreeDataProvider(await directoryController.getRootFolderPath()), - }); + const sidebarController = new SidebarController( + "assayCommands", + directoryController + ); + const { refresh, treeView: sidebarTreeViewDisposable } = + await sidebarController.getTreeView(); + + const sidebarRefreshDisposable = vscode.commands.registerCommand( + "assay.refresh", + refresh + ); + + const viewAddonDisposable = vscode.commands.registerCommand( + "assay.viewAddon", + urlController.viewAddon, + urlController + ); + + const diffDisposable = vscode.commands.registerCommand( + "assay.sidebarDiff", + diffController.diffFromSidebar, + diffController + ); const assayUpdaterDisposable = vscode.commands.registerCommand( "assay.checkForUpdates", @@ -105,7 +124,10 @@ export async function activate(context: vscode.ExtensionContext) { apiKeyDisposable, apiSecretDisposable, apiCredentialsTestDisposable, - sidebarDisposable, + sidebarRefreshDisposable, + sidebarTreeViewDisposable, + viewAddonDisposable, + diffDisposable, assayUpdaterDisposable, handleRootConfigurationChangeDisposable ); @@ -179,17 +201,6 @@ export async function activate(context: vscode.ExtensionContext) { statusBarController ); - const diffDisposable = vscode.commands.registerCommand( - "assay.openInDiffTool", - async (_e: vscode.Uri, uris?: [vscode.Uri, vscode.Uri]) => { - if (!uris) { - return; - } - await diffController.openInDiffTool(uris); - }, - diffController - ); - const exportCommentsFolderDisposable = vscode.commands.registerCommand( "assay.exportCommentsFromContext", commentCacheController.exportVersionComments, @@ -256,7 +267,6 @@ export async function activate(context: vscode.ExtensionContext) { ); context.subscriptions.push( - diffDisposable, updateStatusBarController, fileDecorationProviderDisposable, commentController.controller, diff --git a/src/model/sidebarTreeDataProvider.ts b/src/model/sidebarTreeDataProvider.ts new file mode 100644 index 00000000..fce376fd --- /dev/null +++ b/src/model/sidebarTreeDataProvider.ts @@ -0,0 +1,90 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; + +// Does not use resourceUri to avoid fileDecorationProvider providing to this tree. +export class AddonTreeItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly uri: vscode.Uri, + public readonly contextValue: string | undefined, + showIcon?: boolean + ) { + super(label, collapsibleState); + this.iconPath = showIcon + ? { + dark: path.join( + __filename, + "..", + "..", + "media", + "sidebarIcons", + "puzzle_inverse.svg" + ), + light: path.join( + __filename, + "..", + "..", + "media", + "sidebarIcons", + "puzzle.svg" + ), + } + : undefined; + } +} + +export class AddonTreeDataProvider + implements vscode.TreeDataProvider +{ + private _onDidChangeTreeData: vscode.EventEmitter = + new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = + this._onDidChangeTreeData.event; + + constructor(private rootPath: string) {} + + getTreeItem(element: AddonTreeItem): vscode.TreeItem { + return element; + } + + getChildren(element?: AddonTreeItem): Thenable { + const itemPath = element?.uri ? element.uri.fsPath : this.rootPath; + const depth = itemPath.split(this.rootPath)?.at(1)?.split("/").length; + return new Promise((resolve) => { + fs.readdir(itemPath, (err, files) => { + if (err || !depth || depth > 2) { + return resolve([]); + } + + const children: AddonTreeItem[] = []; + files.map((file) => { + const filePath = path.join(itemPath, file); + const isDirectory = fs.statSync(filePath).isDirectory(); + if (isDirectory) { + const isGuidFolder = depth < 2; + const contextValue = isGuidFolder ? "guidDirectory" : undefined; + const collapsibleState = isGuidFolder + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.None; + children.push( + new AddonTreeItem( + file, + collapsibleState, + vscode.Uri.file(filePath), + contextValue, + isGuidFolder + ) + ); + } + }); + return resolve(children); + }); + }); + } + + refresh(): void { + this._onDidChangeTreeData.fire(undefined); + } +} diff --git a/src/views/test.ts b/src/views/test.ts deleted file mode 100644 index eac281d9..00000000 --- a/src/views/test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as vscode from 'vscode'; - -class DirectoryTreeItem extends vscode.TreeItem { - constructor( - public readonly label: string, - public readonly collapsibleState: vscode.TreeItemCollapsibleState, - public readonly resourceUri: vscode.Uri, - public readonly contextValue: string | undefined, - showIcon?: boolean - ) { - super(label, collapsibleState); - - this.iconPath = showIcon ? { - dark: path.join(__filename, '..', '..', 'media', 'puzzle_inverse.svg'), - light: path.join(__filename, '..', '..', 'media', 'puzzle.svg'), - } : undefined; - - } -} - -export class SidebarTreeDataProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - - constructor(private rootPath: string) { - - } - - getTreeItem(element: DirectoryTreeItem): vscode.TreeItem { - return element; - } - - // Get the children of the specified element (or root if no element is provided) - getChildren(element?: DirectoryTreeItem): Thenable { - - - const basePath = element?.resourceUri ? element.resourceUri.fsPath : this.rootPath; - - const depth = basePath.split(this.rootPath)?.at(1)?.split("/").length; - - - - return new Promise(resolve => { - fs.readdir(basePath, (err, files) => { - - if(err || !depth || depth > 2){ - return resolve([]); - } - - const children: DirectoryTreeItem[] = []; - - files.map(file => { - const filePath = path.join(basePath, file); - const isDirectory = fs.statSync(filePath).isDirectory(); - - if(isDirectory){ - const isGuidFolder = depth < 2; - const contextValue = isGuidFolder ? "guidDirectory" : undefined; - const collapsibleState = isGuidFolder ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None; - children.push(new DirectoryTreeItem(file, collapsibleState, vscode.Uri.file(filePath), contextValue, isGuidFolder)); - } - - }); - - return resolve(children); - - }); - }); - } - - refresh(): void { - this._onDidChangeTreeData.fire(); - } -} \ No newline at end of file