Skip to content

repl: Save notebooks through the project instead of the local filesystem - #62602

Open
ArneshBanerjee wants to merge 2 commits into
zed-industries:mainfrom
ArneshBanerjee:fix-remote-notebook-save
Open

repl: Save notebooks through the project instead of the local filesystem#62602
ArneshBanerjee wants to merge 2 commits into
zed-industries:mainfrom
ArneshBanerjee:fix-remote-notebook-save

Conversation

@ArneshBanerjee

Copy link
Copy Markdown
Contributor

Closes #59001

Saving a notebook wrote the file with project.fs().atomic_write(abs_path, ...). Project::fs() is always the client's own filesystem, so for a remote project the remote path was resolved against the local machine: on Windows the drive letter got prepended and the atomic-write temp file used a backslash, producing C:/data/project/src_example\.tmpPuzw3d3 and os error 3. The same thing fails on macOS/Linux clients with os error 2.

Both save and save_as now go through the project's buffer machinery instead: open the buffer for the notebook's ProjectPath, set its text to the serialized notebook, then save_buffer / save_buffer_as. Writes are routed over the remote connection for remote projects, and the open buffer stays in sync locally.

save_as also updates the item's project path and entry id, since save_buffer_as moves the buffer to the new path and otherwise the next save would write back to the old file.

Added a test that edits a cell, saves, and checks that the notebook buffer held by the project reflects the saved file. It fails against the old implementation.

Release Notes:

  • Fixed saving Jupyter notebooks on remote projects failing with "The system cannot find the path specified"

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 13, 2026
@dinocosta dinocosta added area:repl repl, jupyter, notebooks, etc platform:remote Remote development, SSH and zed-remote-server labels Aug 14, 2026

@MrSubidubi MrSubidubi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this!

Looks good overall, will give it a second pass after the comment below

Comment on lines 1852 to 1875
fn save(
&mut self,
_options: SaveOptions,
project: Entity<Project>,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let notebook = self.to_notebook(cx);
let path = self.notebook_item.read(cx).path.clone();
let fs = project.read(cx).fs().clone();
let project_path = self.notebook_item.read(cx).project_path.clone();

self.mark_as_saved(cx);

cx.spawn(async move |_this, _cx| {
cx.spawn(async move |_this, cx| {
let json =
serde_json::to_string_pretty(&notebook).context("Failed to serialize notebook")?;
fs.atomic_write(path, json).await?;
Ok(())
let buffer = project
.update(cx, |project, cx| project.open_buffer(project_path, cx))
.await?;
buffer.update(cx, |buffer, cx| buffer.set_text(json, cx));
project
.update(cx, |project, cx| project.save_buffer(buffer, cx))
.await
})
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The methods save and save_as are very identical at this point, and contain a lot of duplicate code.

Can we perhaps throw this into a method save_impl that takes everything needed plus an enum that lets us differentiate between Save and SaveAs, then do all the different stuff based on that enum?

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.

Sure, will take a look at it.

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.

Done, both now go through a shared save_impl that takes a SaveDestination enum (CurrentPath / NewPath). The only branch left is save_buffer vs save_buffer_as plus the path update that has to follow the move.

@ArneshBanerjee

Copy link
Copy Markdown
Contributor Author

Pushed the refactor. save and save_as now both call a shared save_impl that takes a SaveDestination enum, so the only thing that differs is save_buffer vs save_buffer_as and the path update that follows the move.

Tests and clippy are green locally. Ready for another look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:repl repl, jupyter, notebooks, etc cla-signed The user has signed the Contributor License Agreement platform:remote Remote development, SSH and zed-remote-server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Saving a remote (Linux) .ipynb from a Windows client fails: temp path built with Windows semantics (drive letter + backslash) → os error 3

3 participants