Skip to content

Commit 5a19d38

Browse files
committed
Add create file support to WSFS explorer
1 parent a56cb82 commit 5a19d38

4 files changed

Lines changed: 173 additions & 1 deletion

File tree

packages/databricks-vscode/package.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,20 @@
196196
"enablement": "databricks.context.activated && databricks.context.loggedIn && !databricks.context.remoteMode",
197197
"category": "Databricks"
198198
},
199+
{
200+
"command": "databricks.wsfs.createNewFile",
201+
"title": "Create File",
202+
"icon": "$(new-file)",
203+
"enablement": "databricks.context.activated && databricks.context.loggedIn && !databricks.context.remoteMode",
204+
"category": "Databricks"
205+
},
206+
{
207+
"command": "databricks.wsfs.createNewFile.toolbar",
208+
"title": "Create File",
209+
"icon": "$(new-file)",
210+
"enablement": "databricks.context.activated && databricks.context.loggedIn && !databricks.context.remoteMode",
211+
"category": "Databricks"
212+
},
199213
{
200214
"command": "databricks.wsfs.openInBrowser",
201215
"title": "Open in Browser",
@@ -726,6 +740,11 @@
726740
"when": "view == workspaceFsView",
727741
"group": "navigation@1"
728742
},
743+
{
744+
"command": "databricks.wsfs.createNewFile.toolbar",
745+
"when": "view == workspaceFsView",
746+
"group": "navigation@1"
747+
},
729748
{
730749
"command": "databricks.unityCatalog.filter",
731750
"when": "view == unityCatalogView",
@@ -803,10 +822,15 @@
803822
"group": "wsfs_mut@0"
804823
},
805824
{
806-
"command": "databricks.wsfs.uploadFile",
825+
"command": "databricks.wsfs.createNewFile",
807826
"when": "view == workspaceFsView && (viewItem == wsfs.directory || viewItem == wsfs.repo)",
808827
"group": "wsfs_mut@1"
809828
},
829+
{
830+
"command": "databricks.wsfs.uploadFile",
831+
"when": "view == workspaceFsView && (viewItem == wsfs.directory || viewItem == wsfs.repo)",
832+
"group": "wsfs_mut@2"
833+
},
810834
{
811835
"command": "databricks.wsfs.downloadFile",
812836
"when": "view == workspaceFsView && (viewItem == wsfs.file || viewItem == wsfs.notebook)",

packages/databricks-vscode/src/extension.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,16 @@ export async function activate(
429429
workspaceFsCommands.createFolderFromToolbar,
430430
workspaceFsCommands
431431
),
432+
telemetry.registerCommand(
433+
"databricks.wsfs.createNewFile",
434+
workspaceFsCommands.createFile,
435+
workspaceFsCommands
436+
),
437+
telemetry.registerCommand(
438+
"databricks.wsfs.createNewFile.toolbar",
439+
workspaceFsCommands.createFileFromToolbar,
440+
workspaceFsCommands
441+
),
432442
telemetry.registerCommand(
433443
"databricks.wsfs.openInBrowser",
434444
workspaceFsCommands.openInBrowser,

packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,59 @@ describe("WorkspaceFsCommands – target folder resolution", () => {
126126
});
127127
});
128128

129+
// createFile mirrors createFolder's target resolution but, like upload,
130+
// checks workspaceClient before reaching getValidRoot.
131+
describe("createFile (context menu)", () => {
132+
beforeEach(() => {
133+
when(mockConnectionManager.workspaceClient).thenReturn({} as any);
134+
});
135+
136+
it("no element → targets root", async () => {
137+
await commands.createFile(undefined);
138+
assert.strictEqual(capturedRootPath, ROOT_PATH);
139+
});
140+
141+
it("element=A → targets A", async () => {
142+
await commands.createFile(entityA);
143+
assert.strictEqual(capturedRootPath, entityA.path);
144+
});
145+
146+
it("element=B while A is selected → targets B", async () => {
147+
fakeTreeView.simulateSelect(entityA);
148+
await commands.createFile(entityB);
149+
assert.strictEqual(capturedRootPath, entityB.path);
150+
});
151+
});
152+
153+
describe("createFileFromToolbar (toolbar)", () => {
154+
beforeEach(() => {
155+
when(mockConnectionManager.workspaceClient).thenReturn({} as any);
156+
});
157+
158+
it("nothing selected → targets root", async () => {
159+
await commands.createFileFromToolbar(undefined);
160+
assert.strictEqual(capturedRootPath, ROOT_PATH);
161+
});
162+
163+
it("A selected, toolbar clicked → targets A", async () => {
164+
fakeTreeView.simulateSelect(entityA);
165+
await commands.createFileFromToolbar(entityA);
166+
assert.strictEqual(capturedRootPath, entityA.path);
167+
});
168+
169+
it("selection cleared before toolbar click → targets root", async () => {
170+
fakeTreeView.simulateSelect(entityA);
171+
fakeTreeView.simulateSelect(undefined);
172+
await commands.createFileFromToolbar(undefined);
173+
assert.strictEqual(capturedRootPath, ROOT_PATH);
174+
});
175+
176+
it("element passed but nothing selected (edge case) → targets root", async () => {
177+
await commands.createFileFromToolbar(entityA);
178+
assert.strictEqual(capturedRootPath, ROOT_PATH);
179+
});
180+
});
181+
129182
describe("uploadFile (context menu)", () => {
130183
// doUploadFile checks workspaceClient before reaching getValidRoot;
131184
// provide a non-null client so root-path resolution is exercised.

packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,91 @@ export class WorkspaceFsCommands implements Disposable {
141141
return created;
142142
}
143143

144+
@withLogContext(Loggers.Extension)
145+
async createFile(element?: WorkspaceFsEntity, @context ctx?: Context) {
146+
const rootPath =
147+
element?.path ??
148+
this.connectionManager.databricksWorkspace?.currentFsRoot.path;
149+
return this.doCreateFile(rootPath, ctx);
150+
}
151+
152+
@withLogContext(Loggers.Extension)
153+
async createFileFromToolbar(
154+
element?: WorkspaceFsEntity,
155+
@context ctx?: Context
156+
) {
157+
const activeElement = this.resolveTargetElementForToolbar(element);
158+
const rootPath =
159+
activeElement?.path ??
160+
this.connectionManager.databricksWorkspace?.currentFsRoot.path;
161+
return this.doCreateFile(rootPath, ctx);
162+
}
163+
164+
@withLogContext(Loggers.Extension)
165+
private async doCreateFile(
166+
rootPath: string | undefined,
167+
@context ctx?: Context
168+
) {
169+
const client = this.connectionManager.workspaceClient;
170+
if (!client) {
171+
window.showErrorMessage("Please login first to create a file");
172+
return;
173+
}
174+
175+
const root = await this.getValidRoot(rootPath, ctx);
176+
if (!root) {
177+
return;
178+
}
179+
180+
const inputName = await createDirWizard(
181+
this.workspaceFolderManager.activeProjectUri,
182+
"File Name",
183+
root
184+
);
185+
186+
if (inputName === undefined) {
187+
return;
188+
}
189+
190+
const existing = await WorkspaceFsEntity.fromPath(
191+
client,
192+
`${root.path}/${inputName}`
193+
);
194+
if (existing) {
195+
const answer = await window.showWarningMessage(
196+
`"${inputName}" already exists in the workspace. Overwrite it?`,
197+
{modal: true},
198+
"Overwrite"
199+
);
200+
if (answer !== "Overwrite") {
201+
return;
202+
}
203+
}
204+
205+
try {
206+
await root.createFile(inputName, "", true);
207+
} catch (e: unknown) {
208+
const msg = e instanceof Error ? e.message : String(e);
209+
window.showErrorMessage(`Failed to create "${inputName}": ${msg}`);
210+
return;
211+
}
212+
213+
this.workspaceFsDataProvider.refresh();
214+
const uri = Uri.from({
215+
scheme: WorkspaceFsFileSystemProvider.scheme,
216+
path: `${root.path}/${inputName}`,
217+
});
218+
this.fsp.notifyCreated(uri);
219+
220+
try {
221+
await window.showTextDocument(
222+
await workspace.openTextDocument(uri)
223+
);
224+
} catch (e: unknown) {
225+
ctx?.logger?.error(`Can't open ${inputName} after creation`, e);
226+
}
227+
}
228+
144229
private async createRepo(repoPath: string) {
145230
const wsClient = this.connectionManager.workspaceClient;
146231
if (!wsClient) {

0 commit comments

Comments
 (0)