Skip to content

Add create file support to WSFS explorer#1922

Merged
misha-db merged 8 commits into
mainfrom
wsfs-add-file
Jun 24, 2026
Merged

Add create file support to WSFS explorer#1922
misha-db merged 8 commits into
mainfrom
wsfs-add-file

Conversation

@misha-db

Copy link
Copy Markdown
Contributor

Changes

Adds the ability to create new files directly in the Databricks Workspace Filesystem (WSFS) explorer, alongside the existing folder-creation and upload commands. Users can now create a file from the view toolbar or from a directory/repo context menu, name it via the existing wizard, and have it open automatically in the editor.

Tests

Updated WorkspaceFsCommands.test.ts

@anton-107 anton-107 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid addition — the command wiring, menu placement, and toolbar-vs-context-menu target resolution cleanly mirror the existing createFolder/uploadFile patterns, and the test coverage is thorough. One correctness/consistency issue should be addressed before merge, plus a couple of minor UX nits (left inline).

Main issue — the .ipynb-only heuristic is too narrow. doCreateFile decides everything from inputName.endsWith(".ipynb"), but the SDK imports with format: "AUTO" and WorkspaceFsDir.createFile strips a broader set on lookup: (py|ipynb|scala|r|sql). That regex is the SDK telling us .py/.scala/.r/.sql are also stored as notebooks (extension dropped). Consequences for e.g. script.py:

  1. It lands in the editor branch and opens created.path (the stripped notebook path) as a text document — inconsistent with .ipynb, which opens in the browser.
  2. The existence pre-check looks at ${root}/script.py while the notebook lands at ${root}/script, so the clash is missed and — since createFile(..., true) passes overwrite=true — an existing notebook is silently overwritten with no prompt. (doUploadFile shares this gap, so it's pre-existing, but worth not propagating.)

Recommend deciding the open mode from the created entity via the already-imported WorkspaceFsUtils.isNotebook(created) instead of the extension string, and widening the existence-check stripping to the SDK's set. See inline comments.

Heads-up on tests: the test fake's createFile only strips .ipynb, so the suite is green but wouldn't catch the above. If you switch to isNotebook(created), please add a test where the fake returns a NOTEBOOK-typed entity for a .py name.

});
this.fsp.notifyCreated(uri);

if (isIpynb && created) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This open-mode decision keys off isIpynb, but WorkspaceFsDir.createFile strips (py|ipynb|scala|r|sql) on lookup — so a .py/.sql/.scala/.r file is also stored as a notebook (extension dropped), and created.path here is the stripped path. Those fall into the editor branch below and get opened as a text document at the stripped notebook path, inconsistent with .ipynb.

Suggest deciding from the actual entity instead of the extension — WorkspaceFsUtils is already imported and isNotebook exists:

if (created && WorkspaceFsUtils.isNotebook(created)) {
    try { await this.openInBrowser(created); }
    catch (e: unknown) { ctx?.logger?.error(`Can't open ${inputName} in browser after creation`, e); }
    return;
}

This removes the brittle string check entirely and makes .py-as-notebook behave like .ipynb.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using WorkspaceFsUtils.isNotebook(created) to detected if created file was notebook

}

const isIpynb = inputName.toLowerCase().endsWith(".ipynb");
const existingName = isIpynb

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existence check only strips .ipynb, but the SDK stores (py|ipynb|scala|r|sql) at the extension-stripped path. So creating script.py when a script notebook already exists won't detect the clash, won't prompt, and — because createFile(..., true) overwrites — silently destroys the existing notebook. Consider stripping the same extension set the SDK does (/\.(py|ipynb|scala|r|sql)$/i) for this lookup.

@misha-db misha-db Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When creating new file, content is empty string "" for .sql, .py, .scala, .r etc.. so it ends up with type FILE, not converted to notebook type automatically. so extension will not be stripped, and no silent overwrite will happen.

in case of ipynb file, valid empty notebook content (json) is provided, so it will be converted to notebook format, that's the reason why we check it explicitly

return;
}

const inputName = await createDirWizard(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: createDirWizard is folder-oriented when reused for filenames — it pre-fills the input value/placeHolder with the project folder basename (an odd default for a new file) and its validation returns "Invalid name: Folders cannot contain '/'". Consider a file-appropriate default and neutral wording.

"group": "navigation@1"
},
{
"command": "databricks.wsfs.createNewFile.toolbar",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (optional): this toolbar button shares navigation@1 with createFolder.toolbar and uploadFile.toolbar, so ordering between the three is implicit. The context-menu entries use explicit wsfs_mut@0/@1/@2; consider the same explicit ordering here for predictability. Pre-existing pattern, so optional.

Comment thread packages/databricks-vscode/package.json
@rugpanov

Copy link
Copy Markdown
Contributor

Looks good from my side, I let @anton-107 to make a pass before approving.

@anton-107 anton-107 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-ups all addressed — approving.

  • Open-mode decision: now branches on WorkspaceFsUtils.isNotebook(created) instead of the extension string, so the open path is driven by the actual created entity. My main concern is resolved.
  • Existence-check stripping: confirmed correct for this flow — new files are created with empty content, so .py/.sql/.scala/.r import as plain FILEs with the extension preserved (only .ipynb converts to a notebook at the stripped path). The created-path and lookup keys stay consistent, so the silent-overwrite case can't occur here. The narrow .ipynb stripping is the right choice.
  • Toolbar regression (selecting a file/notebook): getValidRoot rejects a non-directory target with a clear message, so it's a graceful error rather than a crash.

Remaining items are cosmetic (folder-oriented createDirWizard wording for filenames; implicit navigation@1 toolbar ordering) and non-blocking.

@rugpanov rugpanov temporarily deployed to test-trigger-is June 24, 2026 14:21 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

If integration tests don't run automatically, an authorized user can run them manually by following the instructions below:

Trigger:
go/deco-tests-run/vscode

Inputs:

  • PR number: 1922
  • Commit SHA: c33b34c5ee2ae072233c6f2ddefa0589e9c645be

Checks will be approved automatically on success.

@misha-db misha-db merged commit 430a4d8 into main Jun 24, 2026
6 of 8 checks passed
@rugpanov rugpanov mentioned this pull request Jun 24, 2026
This was referenced Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants