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 <finn@zed.dev>
This commit is contained in:
+51
-10
@@ -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<Task<()>>,
|
||||
git_blame_inline_enabled: bool,
|
||||
render_diff_hunk_controls: RenderDiffHunkControlsFn,
|
||||
serialize_dirty_buffers: bool,
|
||||
buffer_serialization: Option<BufferSerialization>,
|
||||
show_selection_menu: Option<bool>,
|
||||
blame: Option<Entity<GitBlame>>,
|
||||
blame_subscription: Option<Subscription>,
|
||||
@@ -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<WorkspaceId> {
|
||||
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;
|
||||
|
||||
+14
-15
@@ -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<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
})),
|
||||
|
||||
Reference in New Issue
Block a user