Skip to content

Commit 3f5a43f

Browse files
authored
Fix save-as making it easy to save without extension for positron notebooks (#8858)
Addresses #8857. @:notebooks Adds proper file dialog configuration to `PositronNotebookEditorInput` to prevent users from saving notebooks without `.ipynb` extensions. Uses VS Code's `showSaveDialog` with filters, title, and localized labels to provide a native save experience that automatically handles extension enforcement. The dialog's built-in extension enforcement ensures users cannot accidentally save notebooks un-extensioned files that causes the file to display as raw json. ### Release Notes #### New Features #### Bug Fixes - Properly configured the save as dialog for positron notebooks to prevent mangled file names ### QA Notes Updated the positron notebook save e2e test to make sure this behavior is working. At least for the case of saving without a file extension. Test the enhanced save dialog by creating a new untitled notebook and saving it. The dialog should provide a native experience with proper labeling and automatic extension handling. 1. Create a new untitled notebook 2. Add some content (e.g., a code cell with `print("test")`) 3. Use "Save As" and observe: - Dialog shows "Save Notebook As" title - Type `mynotebook` (should automatically become `mynotebook.ipynb`) Expected behavior: The save dialog automatically enforces`.ipynb` extensions, preventing users from accidentally saving raw JSON notebook content.
1 parent 48330b2 commit 3f5a43f

File tree

2 files changed

+21
-9
lines changed

2 files changed

+21
-9
lines changed

src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditorInput.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export class PositronNotebookEditorInput extends EditorInput {
172172
*/
173173
override getName(): string {
174174
const extUri = new ExtUri(() => false);
175-
return extUri.basename(this.resource) ?? localize('positronNotebookInputName', "Positron Notebook");
175+
return extUri.basename(this.resource) ?? localize('positron.notebook.inputName', "Positron Notebook");
176176
}
177177

178178
/**
@@ -237,8 +237,16 @@ export class PositronNotebookEditorInput extends EditorInput {
237237
const suggestedName = extUri.basename(this.resource);
238238
const pathCandidate = await this._suggestName(provider, suggestedName);
239239

240-
// Ask the user where to save the file
241-
const target = await this._fileDialogService.pickFileToSave(pathCandidate, options?.availableFileSystems);
240+
// Ask the user where to save the file with proper filters
241+
const target = await this._fileDialogService.showSaveDialog({
242+
title: localize('positron.notebook.saveAs', "Save Notebook As"),
243+
defaultUri: pathCandidate,
244+
filters: [
245+
// This will ensure that the saved file has the .ipynb extension.
246+
{ name: localize('positron.notebook.fileType', 'Jupyter Notebook'), extensions: ['ipynb'] }
247+
],
248+
availableFileSystems: options?.availableFileSystems
249+
});
242250
if (!target) {
243251
return undefined; // save cancelled
244252
}

test/e2e/tests/notebook/positron-notebook-editor.test.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,22 @@ test.describe('Positron notebook opening and saving', {
9292
// New notebooks should automatically be named "Untitled-1.ipynb" by default
9393
await editors.waitForActiveTab('Untitled-1.ipynb', false);
9494

95-
// Save the notebook with a specific name
95+
// Test save dialog functionality - save with a name that doesn't include .ipynb extension
96+
// The enhanced save dialog should automatically handle extension enforcement
9697
await runCommand('workbench.action.files.saveAs', { keepOpen: true });
9798
await quickInput.waitForQuickInputOpened();
98-
const newFileName = `saved-positron-notebook-${Math.random().toString(36).substring(7)}.ipynb`;
99-
await quickInput.type(path.join(app.workspacePathOrFolder, newFileName));
99+
100+
// Type filename without extension to test automatic extension handling
101+
const baseFileName = `saved-positron-notebook-${Math.random().toString(36).substring(7)}`;
102+
await quickInput.type(path.join(app.workspacePathOrFolder, baseFileName));
100103
await quickInput.clickOkButton();
101104

102-
// Verify the editor tab now shows the new filename instead of "Untitled" and it is a positron notebook
103-
await editors.waitForActiveTab(newFileName, false);
105+
// Verify the file was saved and the .ipynb extension was automatically added
106+
const expectedFileName = `${baseFileName}.ipynb`;
107+
await editors.waitForActiveTab(expectedFileName, false);
104108
await notebooksPositron.expectToBeVisible();
105109

106110
// Keep the test workspace clean for subsequent test runs
107-
await cleanup.removeTestFiles([newFileName]);
111+
await cleanup.removeTestFiles([expectedFileName]);
108112
});
109113
});

0 commit comments

Comments
 (0)