From eb67d31da7880c4f2032ee6c222499aa24b0ba9d Mon Sep 17 00:00:00 2001 From: misha-db Date: Tue, 16 Jun 2026 19:37:20 +0400 Subject: [PATCH 1/3] fix: use correct target folder for createFolder/uploadFile in Workspace FS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extension.ts Switches the Workspace FS panel registration from window.registerTreeDataProvider to window.createTreeView so the TreeView handle (which exposes selection) is available. The resulting workspaceFsTreeView is passed to WorkspaceFsCommands and disposed via context.subscriptions. WorkspaceFsCommands.ts - Accepts TreeView as a new constructor parameter (private readonly treeView). - Tracks the last left-click–selected item in selectedElement via treeView.onDidChangeSelection (cleared to undefined on deselection). - In createFolder and uploadFile, resolves the target folder using a comparison: - If element !== treeView.selection[0] — the command was invoked from a context menu on a different item than the current selection → use element. - Otherwise — title bar button, or context menu on the already-selected item → use selectedElement (which is undefined if the user deselected before clicking, correctly falling back to the workspace root). This fixes two bugs: 1. Title bar click after deselection was targeting the previously selected folder instead of root. 2. Context-menu click on a non-selected item was targeting the selected folder instead of the right-clicked one. --- packages/databricks-vscode/src/extension.ts | 11 +++---- .../src/workspace-fs/WorkspaceFsCommands.ts | 29 +++++++++++++++---- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/databricks-vscode/src/extension.ts b/packages/databricks-vscode/src/extension.ts index c50503da5..55b0afe88 100644 --- a/packages/databricks-vscode/src/extension.ts +++ b/packages/databricks-vscode/src/extension.ts @@ -393,11 +393,15 @@ export async function activate( connectionManager ); const workspaceFsFsp = new WorkspaceFsFileSystemProvider(connectionManager); + const workspaceFsTreeView = window.createTreeView("workspaceFsView", { + treeDataProvider: workspaceFsDataProvider, + }); const workspaceFsCommands = new WorkspaceFsCommands( workspaceFolderManager, connectionManager, workspaceFsDataProvider, - workspaceFsFsp + workspaceFsFsp, + workspaceFsTreeView ); context.subscriptions.push( @@ -409,10 +413,7 @@ export async function activate( } ), workspaceFsFsp, - window.registerTreeDataProvider( - "workspaceFsView", - workspaceFsDataProvider - ), + workspaceFsTreeView, telemetry.registerCommand( "databricks.wsfs.refresh", workspaceFsCommands.refresh, diff --git a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts index cbdc50f29..5a170e34d 100644 --- a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts +++ b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts @@ -5,7 +5,7 @@ import { WorkspaceFsUtils, } from "../sdk-extensions"; import {context, Context} from "@databricks/sdk-experimental/dist/context"; -import {Disposable, Uri, env, window, workspace} from "vscode"; +import {Disposable, TreeView, Uri, env, window, workspace} from "vscode"; import {ConnectionManager} from "../configuration/ConnectionManager"; import {Loggers} from "../logger"; import {createDirWizard} from "./createDirectoryWizard"; @@ -18,13 +18,21 @@ const withLogContext = logging.withLogContext; export class WorkspaceFsCommands implements Disposable { private disposables: Disposable[] = []; + private selectedElement: WorkspaceFsEntity | undefined; constructor( private workspaceFolderManager: WorkspaceFolderManager, private connectionManager: ConnectionManager, private workspaceFsDataProvider: WorkspaceFsDataProvider, - private fsp: WorkspaceFsFileSystemProvider - ) {} + private fsp: WorkspaceFsFileSystemProvider, + private readonly treeView: TreeView + ) { + this.disposables.push( + treeView.onDidChangeSelection((e) => { + this.selectedElement = e.selection[0]; + }) + ); + } @withLogContext(Loggers.Extension) async getValidRoot( @@ -72,8 +80,12 @@ export class WorkspaceFsCommands implements Disposable { @withLogContext(Loggers.Extension) async createFolder(element?: WorkspaceFsEntity, @context ctx?: Context) { + const activeElement = + element !== this.treeView.selection[0] + ? element + : this.selectedElement; const rootPath = - element?.path ?? + activeElement?.path ?? this.connectionManager.databricksWorkspace?.currentFsRoot.path; const root = await this.getValidRoot(rootPath, ctx); @@ -179,9 +191,14 @@ export class WorkspaceFsCommands implements Disposable { return; } + const activeElement = + element !== this.treeView.selection[0] + ? element + : this.selectedElement; const rootPath = - (element?.type === "DIRECTORY" || element?.type === "REPO" - ? element?.path + (activeElement?.type === "DIRECTORY" || + activeElement?.type === "REPO" + ? activeElement?.path : undefined) ?? this.connectionManager.databricksWorkspace?.currentFsRoot.path; From 5bdab7a3a3a204b98928f00010ee181d715447ef Mon Sep 17 00:00:00 2001 From: misha-db Date: Tue, 16 Jun 2026 20:13:14 +0400 Subject: [PATCH 2/3] Add tests, cleanup --- .../workspace-fs/WorkspaceFsCommands.test.ts | 166 ++++++++++++++++++ .../src/workspace-fs/WorkspaceFsCommands.ts | 18 +- 2 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts diff --git a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts new file mode 100644 index 000000000..4f600854b --- /dev/null +++ b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts @@ -0,0 +1,166 @@ +import assert from "assert"; +import {EventEmitter, TreeView} from "vscode"; +import {mock, instance, when} from "ts-mockito"; +import {WorkspaceFsCommands} from "./WorkspaceFsCommands"; +import {WorkspaceFsEntity} from "../sdk-extensions"; +import {ConnectionManager} from "../configuration/ConnectionManager"; +import {WorkspaceFsDataProvider} from "./WorkspaceFsDataProvider"; +import {WorkspaceFsFileSystemProvider} from "./WorkspaceFsFileSystemProvider"; +import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager"; +import {DatabricksWorkspace} from "../configuration/DatabricksWorkspace"; +import {RemoteUri} from "../sync/SyncDestination"; + +// Minimal fake TreeView that tracks selection and fires onDidChangeSelection +class FakeTreeView { + selection: ReadonlyArray = []; + private _emitter = new EventEmitter<{ + selection: WorkspaceFsEntity[]; + }>(); + readonly onDidChangeSelection = this._emitter.event; + + simulateSelect(element: WorkspaceFsEntity | undefined): void { + this.selection = element ? [element] : []; + this._emitter.fire({selection: [...this.selection]}); + } +} + +function makeEntity(path: string): WorkspaceFsEntity { + return {path, type: "DIRECTORY"} as unknown as WorkspaceFsEntity; +} + +describe("WorkspaceFsCommands – target folder resolution", () => { + const ROOT_PATH = "/Users/me"; + + let fakeTreeView: FakeTreeView; + let mockConnectionManager: ConnectionManager; + let commands: WorkspaceFsCommands; + let capturedRootPath: string | undefined; + + const entityA = makeEntity("/Users/me/A"); + const entityB = makeEntity("/Users/me/B"); + + beforeEach(() => { + fakeTreeView = new FakeTreeView(); + + const mockDatabricksWorkspace = mock(); + when(mockDatabricksWorkspace.currentFsRoot).thenReturn( + new RemoteUri(ROOT_PATH) + ); + + mockConnectionManager = mock(); + when(mockConnectionManager.workspaceClient).thenReturn( + undefined as any + ); + when(mockConnectionManager.databricksWorkspace).thenReturn( + instance(mockDatabricksWorkspace) + ); + + commands = new WorkspaceFsCommands( + instance(mock()), + instance(mockConnectionManager), + instance(mock()), + instance(mock()), + fakeTreeView as unknown as TreeView + ); + + // Replace getValidRoot to capture rootPath and short-circuit execution + capturedRootPath = undefined; + (commands as any).getValidRoot = async (rootPath?: string) => { + capturedRootPath = rootPath; + return undefined; + }; + }); + + describe("createFolder", () => { + it("title bar, nothing selected → targets root", async () => { + await commands.createFolder(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + + it("title bar, A selected → targets A", async () => { + fakeTreeView.simulateSelect(entityA); + await commands.createFolder(entityA); + assert.strictEqual(capturedRootPath, entityA.path); + }); + + it("title bar, A selected then deselected → targets root", async () => { + fakeTreeView.simulateSelect(entityA); + fakeTreeView.simulateSelect(undefined); + await commands.createFolder(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + + it("context menu on B while A is selected → targets B", async () => { + fakeTreeView.simulateSelect(entityA); + // Right-click on B does NOT update selection; selection[0] is still A + await commands.createFolder(entityB); + assert.strictEqual(capturedRootPath, entityB.path); + }); + + it("context menu on B with nothing selected → targets B", async () => { + await commands.createFolder(entityB); + assert.strictEqual(capturedRootPath, entityB.path); + }); + + it("context menu on A while A is selected → targets A", async () => { + fakeTreeView.simulateSelect(entityA); + // Right-click on the already-selected item: element === selection[0] + await commands.createFolder(entityA); + assert.strictEqual(capturedRootPath, entityA.path); + }); + }); + + describe("uploadFile", () => { + // uploadFile bails early when workspaceClient is undefined, before + // reaching getValidRoot. Provide a non-null client so the activeElement + // logic is exercised. + beforeEach(() => { + when(mockConnectionManager.workspaceClient).thenReturn({} as any); + }); + + it("title bar, nothing selected → targets root", async () => { + await commands.uploadFile(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + + it("title bar, A selected → targets A", async () => { + fakeTreeView.simulateSelect(entityA); + await commands.uploadFile(entityA); + assert.strictEqual(capturedRootPath, entityA.path); + }); + + it("title bar, A selected then deselected → targets root", async () => { + fakeTreeView.simulateSelect(entityA); + fakeTreeView.simulateSelect(undefined); + await commands.uploadFile(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + + it("context menu on B while A is selected → targets B", async () => { + fakeTreeView.simulateSelect(entityA); + await commands.uploadFile(entityB); + assert.strictEqual(capturedRootPath, entityB.path); + }); + + it("context menu on B with nothing selected → targets B", async () => { + await commands.uploadFile(entityB); + assert.strictEqual(capturedRootPath, entityB.path); + }); + + it("context menu on A while A is selected → targets A", async () => { + fakeTreeView.simulateSelect(entityA); + await commands.uploadFile(entityA); + assert.strictEqual(capturedRootPath, entityA.path); + }); + + it("title bar, file (non-directory) selected → targets root", async () => { + const fileEntity = { + path: "/Users/me/A/note.py", + type: "FILE", + } as unknown as WorkspaceFsEntity; + fakeTreeView.simulateSelect(fileEntity); + await commands.uploadFile(fileEntity); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + }); +}); diff --git a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts index 5a170e34d..bc493646a 100644 --- a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts +++ b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts @@ -78,12 +78,17 @@ export class WorkspaceFsCommands implements Disposable { return root; } + private resolveTargetElement( + element?: WorkspaceFsEntity + ): WorkspaceFsEntity | undefined { + return element !== this.treeView.selection[0] + ? element + : this.selectedElement; + } + @withLogContext(Loggers.Extension) async createFolder(element?: WorkspaceFsEntity, @context ctx?: Context) { - const activeElement = - element !== this.treeView.selection[0] - ? element - : this.selectedElement; + const activeElement = this.resolveTargetElement(element); const rootPath = activeElement?.path ?? this.connectionManager.databricksWorkspace?.currentFsRoot.path; @@ -191,10 +196,7 @@ export class WorkspaceFsCommands implements Disposable { return; } - const activeElement = - element !== this.treeView.selection[0] - ? element - : this.selectedElement; + const activeElement = this.resolveTargetElement(element); const rootPath = (activeElement?.type === "DIRECTORY" || activeElement?.type === "REPO" From f19b9772d5864b62c16219a45d38e79325033d53 Mon Sep 17 00:00:00 2001 From: misha-db Date: Wed, 17 Jun 2026 19:08:59 +0400 Subject: [PATCH 3/3] Split uploadFile into toolbar and context-menu commands When the toolbar button is clicked, VS Code passes the currently-selected tree node as `element`, making it impossible to distinguish toolbar vs context-menu invocations from a single handler. Changes: - Add `databricks.wsfs.uploadFile.toolbar` command bound to `view/title`; the existing `databricks.wsfs.uploadFile` stays on `view/item/context` only. - Both handlers resolve the target folder directly from `element` (toolbar variant via `resolveTargetElementForToolbar`, which gates on `treeView.selection` to detect whether anything is truly selected). - Extract shared upload logic into private `doUploadFile`. - Remove the now-unused `resolveTargetElement` helper. - Update unit tests: rename mislabelled "title bar" cases, add dedicated suites for `createFolderFromToolbar` and `uploadFileFromToolbar` covering the full selection-state matrix. --- packages/databricks-vscode/package.json | 25 +++- packages/databricks-vscode/src/extension.ts | 10 ++ .../workspace-fs/WorkspaceFsCommands.test.ts | 114 +++++++++++------- .../src/workspace-fs/WorkspaceFsCommands.ts | 64 +++++++--- 4 files changed, 147 insertions(+), 66 deletions(-) diff --git a/packages/databricks-vscode/package.json b/packages/databricks-vscode/package.json index a0a2f804c..26b6c4614 100644 --- a/packages/databricks-vscode/package.json +++ b/packages/databricks-vscode/package.json @@ -189,6 +189,13 @@ "enablement": "databricks.context.activated && databricks.context.loggedIn && !databricks.context.remoteMode", "category": "Databricks" }, + { + "command": "databricks.wsfs.createFolder.toolbar", + "title": "Create Folder", + "icon": "$(new-folder)", + "enablement": "databricks.context.activated && databricks.context.loggedIn && !databricks.context.remoteMode", + "category": "Databricks" + }, { "command": "databricks.wsfs.openInBrowser", "title": "Open in Browser", @@ -213,6 +220,12 @@ "icon": "$(cloud-upload)", "category": "Databricks" }, + { + "command": "databricks.wsfs.uploadFile.toolbar", + "title": "Upload File", + "icon": "$(cloud-upload)", + "category": "Databricks" + }, { "command": "databricks.wsfs.downloadFile", "title": "Download", @@ -709,7 +722,7 @@ "group": "navigation@1" }, { - "command": "databricks.wsfs.createFolder", + "command": "databricks.wsfs.createFolder.toolbar", "when": "view == workspaceFsView", "group": "navigation@1" }, @@ -724,7 +737,7 @@ "group": "navigation@1" }, { - "command": "databricks.wsfs.uploadFile", + "command": "databricks.wsfs.uploadFile.toolbar", "when": "view == workspaceFsView", "group": "navigation@1" }, @@ -1113,6 +1126,10 @@ "command": "databricks.wsfs.createFolder", "when": "!databricks.context.remoteMode" }, + { + "command": "databricks.wsfs.createFolder.toolbar", + "when": "false" + }, { "command": "databricks.wsfs.refresh", "when": "!databricks.context.remoteMode" @@ -1137,6 +1154,10 @@ "command": "databricks.wsfs.uploadFile", "when": "false" }, + { + "command": "databricks.wsfs.uploadFile.toolbar", + "when": "false" + }, { "command": "databricks.utils.openExternal", "when": "false" diff --git a/packages/databricks-vscode/src/extension.ts b/packages/databricks-vscode/src/extension.ts index 55b0afe88..69ca1fc94 100644 --- a/packages/databricks-vscode/src/extension.ts +++ b/packages/databricks-vscode/src/extension.ts @@ -424,6 +424,11 @@ export async function activate( workspaceFsCommands.createFolder, workspaceFsCommands ), + telemetry.registerCommand( + "databricks.wsfs.createFolder.toolbar", + workspaceFsCommands.createFolderFromToolbar, + workspaceFsCommands + ), telemetry.registerCommand( "databricks.wsfs.openInBrowser", workspaceFsCommands.openInBrowser, @@ -444,6 +449,11 @@ export async function activate( workspaceFsCommands.uploadFile, workspaceFsCommands ), + telemetry.registerCommand( + "databricks.wsfs.uploadFile.toolbar", + workspaceFsCommands.uploadFileFromToolbar, + workspaceFsCommands + ), telemetry.registerCommand( "databricks.wsfs.downloadFile", workspaceFsCommands.downloadFile, diff --git a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts index 4f600854b..3c7c197c7 100644 --- a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts +++ b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts @@ -38,6 +38,10 @@ describe("WorkspaceFsCommands – target folder resolution", () => { const entityA = makeEntity("/Users/me/A"); const entityB = makeEntity("/Users/me/B"); + const fileEntity = { + path: "/Users/me/A/note.py", + type: "FILE", + } as unknown as WorkspaceFsEntity; beforeEach(() => { fakeTreeView = new FakeTreeView(); @@ -71,95 +75,117 @@ describe("WorkspaceFsCommands – target folder resolution", () => { }; }); - describe("createFolder", () => { - it("title bar, nothing selected → targets root", async () => { + // Context-menu command: element is the right-clicked node; treeView + // selection is irrelevant. + describe("createFolder (context menu)", () => { + it("no element → targets root", async () => { await commands.createFolder(undefined); assert.strictEqual(capturedRootPath, ROOT_PATH); }); - it("title bar, A selected → targets A", async () => { - fakeTreeView.simulateSelect(entityA); + it("element=A → targets A", async () => { await commands.createFolder(entityA); assert.strictEqual(capturedRootPath, entityA.path); }); - it("title bar, A selected then deselected → targets root", async () => { - fakeTreeView.simulateSelect(entityA); - fakeTreeView.simulateSelect(undefined); - await commands.createFolder(undefined); - assert.strictEqual(capturedRootPath, ROOT_PATH); - }); - - it("context menu on B while A is selected → targets B", async () => { + it("element=B while A is selected → targets B", async () => { fakeTreeView.simulateSelect(entityA); - // Right-click on B does NOT update selection; selection[0] is still A await commands.createFolder(entityB); assert.strictEqual(capturedRootPath, entityB.path); }); + }); - it("context menu on B with nothing selected → targets B", async () => { - await commands.createFolder(entityB); - assert.strictEqual(capturedRootPath, entityB.path); + // Toolbar command: VS Code passes the currently-selected node as element. + // resolveTargetElementForToolbar uses treeView.selection to detect whether + // something is truly selected; if not, it falls back to root. + describe("createFolderFromToolbar (toolbar)", () => { + it("nothing selected → targets root", async () => { + await commands.createFolderFromToolbar(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); }); - it("context menu on A while A is selected → targets A", async () => { + it("A selected, toolbar clicked → targets A", async () => { fakeTreeView.simulateSelect(entityA); - // Right-click on the already-selected item: element === selection[0] - await commands.createFolder(entityA); + // VS Code passes the selected node as element on toolbar click + await commands.createFolderFromToolbar(entityA); assert.strictEqual(capturedRootPath, entityA.path); }); + + it("selection cleared before toolbar click → targets root", async () => { + fakeTreeView.simulateSelect(entityA); + fakeTreeView.simulateSelect(undefined); + await commands.createFolderFromToolbar(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + + it("element passed but nothing selected (edge case) → targets root", async () => { + // treeView.selection is empty; resolveTargetElementForToolbar + // ignores the element and returns undefined → root + await commands.createFolderFromToolbar(entityA); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); }); - describe("uploadFile", () => { - // uploadFile bails early when workspaceClient is undefined, before - // reaching getValidRoot. Provide a non-null client so the activeElement - // logic is exercised. + describe("uploadFile (context menu)", () => { + // doUploadFile checks workspaceClient before reaching getValidRoot; + // provide a non-null client so root-path resolution is exercised. beforeEach(() => { when(mockConnectionManager.workspaceClient).thenReturn({} as any); }); - it("title bar, nothing selected → targets root", async () => { + it("no element → targets root", async () => { await commands.uploadFile(undefined); assert.strictEqual(capturedRootPath, ROOT_PATH); }); - it("title bar, A selected → targets A", async () => { - fakeTreeView.simulateSelect(entityA); + it("element=A (directory) → targets A", async () => { await commands.uploadFile(entityA); assert.strictEqual(capturedRootPath, entityA.path); }); - it("title bar, A selected then deselected → targets root", async () => { + it("element=B while A is selected → targets B", async () => { fakeTreeView.simulateSelect(entityA); - fakeTreeView.simulateSelect(undefined); - await commands.uploadFile(undefined); + await commands.uploadFile(entityB); + assert.strictEqual(capturedRootPath, entityB.path); + }); + + it("element=file (non-directory) → targets root", async () => { + await commands.uploadFile(fileEntity); assert.strictEqual(capturedRootPath, ROOT_PATH); }); + }); - it("context menu on B while A is selected → targets B", async () => { - fakeTreeView.simulateSelect(entityA); - await commands.uploadFile(entityB); - assert.strictEqual(capturedRootPath, entityB.path); + describe("uploadFileFromToolbar (toolbar)", () => { + beforeEach(() => { + when(mockConnectionManager.workspaceClient).thenReturn({} as any); }); - it("context menu on B with nothing selected → targets B", async () => { - await commands.uploadFile(entityB); - assert.strictEqual(capturedRootPath, entityB.path); + it("nothing selected → targets root", async () => { + await commands.uploadFileFromToolbar(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); }); - it("context menu on A while A is selected → targets A", async () => { + it("A selected, toolbar clicked → targets A", async () => { fakeTreeView.simulateSelect(entityA); - await commands.uploadFile(entityA); + await commands.uploadFileFromToolbar(entityA); assert.strictEqual(capturedRootPath, entityA.path); }); - it("title bar, file (non-directory) selected → targets root", async () => { - const fileEntity = { - path: "/Users/me/A/note.py", - type: "FILE", - } as unknown as WorkspaceFsEntity; + it("selection cleared before toolbar click → targets root", async () => { + fakeTreeView.simulateSelect(entityA); + fakeTreeView.simulateSelect(undefined); + await commands.uploadFileFromToolbar(undefined); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + + it("element passed but nothing selected (edge case) → targets root", async () => { + await commands.uploadFileFromToolbar(entityA); + assert.strictEqual(capturedRootPath, ROOT_PATH); + }); + + it("file selected, toolbar clicked → targets root", async () => { fakeTreeView.simulateSelect(fileEntity); - await commands.uploadFile(fileEntity); + await commands.uploadFileFromToolbar(fileEntity); assert.strictEqual(capturedRootPath, ROOT_PATH); }); }); diff --git a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts index bc493646a..4caac439e 100644 --- a/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts +++ b/packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts @@ -18,7 +18,6 @@ const withLogContext = logging.withLogContext; export class WorkspaceFsCommands implements Disposable { private disposables: Disposable[] = []; - private selectedElement: WorkspaceFsEntity | undefined; constructor( private workspaceFolderManager: WorkspaceFolderManager, @@ -26,13 +25,7 @@ export class WorkspaceFsCommands implements Disposable { private workspaceFsDataProvider: WorkspaceFsDataProvider, private fsp: WorkspaceFsFileSystemProvider, private readonly treeView: TreeView - ) { - this.disposables.push( - treeView.onDidChangeSelection((e) => { - this.selectedElement = e.selection[0]; - }) - ); - } + ) {} @withLogContext(Loggers.Extension) async getValidRoot( @@ -78,21 +71,40 @@ export class WorkspaceFsCommands implements Disposable { return root; } - private resolveTargetElement( + @withLogContext(Loggers.Extension) + async createFolder(element?: WorkspaceFsEntity, @context ctx?: Context) { + const rootPath = + element?.path ?? + this.connectionManager.databricksWorkspace?.currentFsRoot.path; + return this.doCreateFolder(rootPath, ctx); + } + + private resolveTargetElementForToolbar( element?: WorkspaceFsEntity ): WorkspaceFsEntity | undefined { - return element !== this.treeView.selection[0] - ? element - : this.selectedElement; + if (this.treeView.selection[0] === undefined) { + return undefined; + } + return element; } @withLogContext(Loggers.Extension) - async createFolder(element?: WorkspaceFsEntity, @context ctx?: Context) { - const activeElement = this.resolveTargetElement(element); + async createFolderFromToolbar( + element?: WorkspaceFsEntity, + @context ctx?: Context + ) { + const activeElement = this.resolveTargetElementForToolbar(element); const rootPath = activeElement?.path ?? this.connectionManager.databricksWorkspace?.currentFsRoot.path; + return this.doCreateFolder(rootPath, ctx); + } + @withLogContext(Loggers.Extension) + private async doCreateFolder( + rootPath: string | undefined, + @context ctx?: Context + ) { const root = await this.getValidRoot(rootPath, ctx); if (!root) { return; @@ -190,19 +202,31 @@ export class WorkspaceFsCommands implements Disposable { } async uploadFile(element?: WorkspaceFsEntity) { - const client = this.connectionManager.workspaceClient; - if (!client) { - window.showErrorMessage("Please login first to upload a file"); - return; - } + const rootPath = + (element?.type === "DIRECTORY" || element?.type === "REPO" + ? element?.path + : undefined) ?? + this.connectionManager.databricksWorkspace?.currentFsRoot.path; + return this.doUploadFile(rootPath); + } - const activeElement = this.resolveTargetElement(element); + async uploadFileFromToolbar(element?: WorkspaceFsEntity) { + const activeElement = this.resolveTargetElementForToolbar(element); const rootPath = (activeElement?.type === "DIRECTORY" || activeElement?.type === "REPO" ? activeElement?.path : undefined) ?? this.connectionManager.databricksWorkspace?.currentFsRoot.path; + return this.doUploadFile(rootPath); + } + + private async doUploadFile(rootPath: string | undefined) { + const client = this.connectionManager.workspaceClient; + if (!client) { + window.showErrorMessage("Please login first to upload a file"); + return; + } const root = await this.getValidRoot(rootPath); if (!root) {