Skip to content

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

Merged
MrSubidubi merged 3 commits into
zed-industries:mainfrom
ArneshBanerjee:fix-remote-notebook-save
Aug 15, 2026
Merged

MrSubidubi merged 3 commits into
zed-industries:mainfrom
ArneshBanerjee:fix-remote-notebook-save

Conversation

@ArneshBanerjee

@ArneshBanerjee ArneshBanerjee commented Aug 13, 2026

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:

  • N/A

@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.

Comment thread crates/repl/src/notebook/notebook_ui.rs Outdated

@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.

Nice, like this much more now. Good fix, good test, good PR. Thank you so much!

@MrSubidubi
MrSubidubi enabled auto-merge August 15, 2026 19:49
@MrSubidubi
MrSubidubi added this pull request to the merge queue Aug 15, 2026
Merged via the queue into zed-industries:main with commit bc538de Aug 15, 2026
38 checks passed
playdohface pushed a commit to playdohface/zed that referenced this pull request Aug 29, 2026
…tem (zed-industries#62602)

Closes zed-industries#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:

- N/A

---------

Co-authored-by: Finn Evers <finn.evers@outlook.de>
sergiooroman pushed a commit to sergiooroman/zed that referenced this pull request Sep 8, 2026
…tem (zed-industries#62602)

Closes zed-industries#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:

- N/A

---------

Co-authored-by: Finn Evers <finn.evers@outlook.de>
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