From 7a37dd9433dde95c1e03496326a870dcfaced504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CA=9F=E1=B4=9C=C9=B4=E1=B4=87x?= <169813237+im-lunex@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:30:03 +0600 Subject: [PATCH] Do not serialize buffers containing bundled files (#38102) Fix bundled file persistence by introducing SerializationMode enum Closes: #38094 What's the issue? Opening bundled files like Default Key Bindings (zed://settings/keymap-default.json) was causing SQLite foreign key constraint errors. The editor was trying to save state for these read-only assets just like regular files, but since bundled files don't have database entries, the foreign key constraint would fail. The fix Replaced the boolean serialize_dirty_buffers flag with a type-safe SerializationMode enum: ```rust pub enum SerializationMode { Enabled, // Regular files persist across sessions Disabled, // Bundled files don't persist } ``` This prevents serialization at the source: workspace_id() returns None for disabled editors, serialize() bails early, and should_serialize() returns false. When opening bundled files, we set the mode to Disabled from the start, so they're treated as transient views that never interact with the persistence layer. Changes - editor.rs: Added SerializationMode enum and updated serialization methods to respect it - items.rs: Guarded should_serialize() to prevent disabled editors from being serialized - zed.rs: Set SerializationMode::Disabled in open_bundled_file() Result Bundled files open cleanly without SQLite errors and don't persist across workspace reloads (expected behavior). Regular file persistence remains unaffected. Release Notes: Fixed SQLite foreign key constraint errors when opening bundled files like Default Key Bindings. --------- Co-authored-by: MrSubidubi --- crates/editor/src/editor.rs | 61 +++++++++++++++++++++++++++++++------ crates/editor/src/items.rs | 29 +++++++++--------- crates/zed/src/zed.rs | 1 + 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 69196feb3473f1ec71088694660aeb3dee18b2bd..19a063d720bb87e605ede1d493795450215f47d7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -812,6 +812,22 @@ impl MinimapVisibility { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BufferSerialization { + All, + NonDirtyBuffers, +} + +impl BufferSerialization { + fn new(restore_unsaved_buffers: bool) -> Self { + if restore_unsaved_buffers { + Self::All + } else { + Self::NonDirtyBuffers + } + } +} + #[derive(Clone, Debug)] struct RunnableTasks { templates: Vec<(TaskSourceKind, TaskTemplate)>, @@ -1129,7 +1145,7 @@ pub struct Editor { show_git_blame_inline_delay_task: Option>, git_blame_inline_enabled: bool, render_diff_hunk_controls: RenderDiffHunkControlsFn, - serialize_dirty_buffers: bool, + buffer_serialization: Option, show_selection_menu: Option, blame: Option>, blame_subscription: Option, @@ -2205,10 +2221,13 @@ impl Editor { git_blame_inline_enabled: full_mode && ProjectSettings::get_global(cx).git.inline_blame.enabled, render_diff_hunk_controls: Arc::new(render_diff_hunk_controls), - serialize_dirty_buffers: !is_minimap - && ProjectSettings::get_global(cx) - .session - .restore_unsaved_buffers, + buffer_serialization: is_minimap.not().then(|| { + BufferSerialization::new( + ProjectSettings::get_global(cx) + .session + .restore_unsaved_buffers, + ) + }), blame: None, blame_subscription: None, tasks: BTreeMap::default(), @@ -2752,6 +2771,14 @@ impl Editor { self.workspace.as_ref()?.0.upgrade() } + /// Returns the workspace serialization ID if this editor should be serialized. + fn workspace_serialization_id(&self, _cx: &App) -> Option { + self.workspace + .as_ref() + .filter(|_| self.should_serialize_buffer()) + .and_then(|workspace| workspace.1) + } + pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> { self.buffer().read(cx).title(cx) } @@ -3000,6 +3027,20 @@ impl Editor { self.auto_replace_emoji_shortcode = auto_replace; } + pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) { + self.buffer_serialization = should_serialize.then(|| { + BufferSerialization::new( + ProjectSettings::get_global(cx) + .session + .restore_unsaved_buffers, + ) + }) + } + + fn should_serialize_buffer(&self) -> bool { + self.buffer_serialization.is_some() + } + pub fn toggle_edit_predictions( &mut self, _: &ToggleEditPrediction, @@ -3216,8 +3257,7 @@ impl Editor { }); if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None - && let Some(workspace_id) = - self.workspace.as_ref().and_then(|workspace| workspace.1) + && let Some(workspace_id) = self.workspace_serialization_id(cx) { let snapshot = self.buffer().read(cx).snapshot(cx); let selections = selections.clone(); @@ -3282,7 +3322,7 @@ impl Editor { data.folds = inmemory_folds; }); - let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else { + let Some(workspace_id) = self.workspace_serialization_id(cx) else { return; }; let background_executor = cx.background_executor().clone(); @@ -21179,8 +21219,9 @@ impl Editor { } let project_settings = ProjectSettings::get_global(cx); - self.serialize_dirty_buffers = - !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers; + self.buffer_serialization = self + .should_serialize_buffer() + .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers)); if self.mode.is_full() { let show_inline_diagnostics = project_settings.diagnostics.inline.enabled; diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 726ac800d601f8d98055d4e577b3af4f9ed436e2..f82a4e7a30f47798e2db00a17b082a88fb6c7239 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,7 +1,7 @@ use crate::{ - Anchor, Autoscroll, Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, FormatTarget, - MultiBuffer, MultiBufferSnapshot, NavigationData, ReportEditorEvent, SearchWithinRange, - SelectionEffects, ToPoint as _, + Anchor, Autoscroll, BufferSerialization, Editor, EditorEvent, EditorSettings, ExcerptId, + ExcerptRange, FormatTarget, MultiBuffer, MultiBufferSnapshot, NavigationData, + ReportEditorEvent, SearchWithinRange, SelectionEffects, ToPoint as _, display_map::HighlightKey, editor_settings::SeedQuerySetting, persistence::{DB, SerializedEditor}, @@ -1256,17 +1256,15 @@ impl SerializableItem for Editor { window: &mut Window, cx: &mut Context, ) -> Option>> { - if self.mode.is_minimap() { - return None; - } - let mut serialize_dirty_buffers = self.serialize_dirty_buffers; - + let buffer_serialization = self.buffer_serialization?; let project = self.project.clone()?; - if project.read(cx).visible_worktrees(cx).next().is_none() { + + let serialize_dirty_buffers = match buffer_serialization { // If we don't have a worktree, we don't serialize, because // projects without worktrees aren't deserialized. - serialize_dirty_buffers = false; - } + BufferSerialization::All => project.read(cx).visible_worktrees(cx).next().is_some(), + BufferSerialization::NonDirtyBuffers => false, + }; if closing && !serialize_dirty_buffers { return None; @@ -1323,10 +1321,11 @@ impl SerializableItem for Editor { } fn should_serialize(&self, event: &Self::Event) -> bool { - matches!( - event, - EditorEvent::Saved | EditorEvent::DirtyChanged | EditorEvent::BufferEdited - ) + self.should_serialize_buffer() + && matches!( + event, + EditorEvent::Saved | EditorEvent::DirtyChanged | EditorEvent::BufferEdited + ) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2d7d47e968e93eef3d455cec9c324a4d4e0cff42..dbbff04b92df1f67c31e20cc33cff78cce877a2f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1922,6 +1922,7 @@ fn open_bundled_file( let mut editor = Editor::for_multibuffer(buffer, Some(project.clone()), window, cx); editor.set_read_only(true); + editor.set_should_serialize(false, cx); editor.set_breadcrumb_header(title.into()); editor })),