Skip to content

Commit

Permalink
Restore unsaved buffers on restart (#13546)
Browse files Browse the repository at this point in the history
This adds the ability for Zed to restore unsaved buffers on restart. The
user is no longer prompted to save/discard/cancel when trying to close a
Zed window with dirty buffers in it. Instead those dirty buffers are
stored and restored on restart.

It does this by saving the contents of dirty buffers to the internal
SQLite database in which Zed stores other data too. On restart, if there
are dirty buffers in the database, they are restored.

On certain events (buffer changed, file saved, ...) Zed will serialize
these buffers, throttled to a 100ms, so that we don't overload the
machine by saving on every keystroke. When Zed quits, it waits until all
the buffers are serialized.


### Current limitations
- It does not persist undo-history (right now we don't persist/restore
undo-history regardless of dirty buffers or not)
- It does not restore buffers in windows without projects/worktrees.
Example: if you open a new window with `cmd-shift-n` and type something
in a buffer, this will _not_ be stored and you will be asked whether to
save/discard on quit. In the future, we want to fix this by also
restoring windows without projects/worktrees.

### Demo



https://github.com/user-attachments/assets/45c63237-8848-471f-8575-ac05496bba19



### Related tickets

I'm unsure about closing them, without also fixing the 2nd limitation:
restoring of worktree-less windows. So let's wait until that.

- #4985
- #4683

### Note on performance

- Serializing editing buffer (asynchronously on background thread) with
500k lines takes ~200ms on M3 Max. That's an extreme case and that
performance seems acceptable.

Release Notes:

- Added automatic restoring of unsaved buffers. Zed can now be closed
even if there are unsaved changes in buffers. One current limitation is
that this only works when having projects open, not single files or
empty windows with unsaved buffers. The feature can be turned off by
setting `{"session": {"restore_unsaved_buffers": false}}`.

---------

Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Antonio <antonio@zed.dev>
  • Loading branch information
3 people authored Jul 17, 2024
1 parent 8e9e94d commit 9241b11
Show file tree
Hide file tree
Showing 20 changed files with 1,102 additions and 331 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 1 addition & 15 deletions crates/diagnostics/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use ui::{h_flex, prelude::*, Icon, IconName, Label};
use util::ResultExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
ItemNavHistory, ToolbarItemLocation, Workspace,
};

actions!(diagnostics, [Deploy, ToggleWarnings]);
Expand Down Expand Up @@ -786,20 +786,6 @@ impl Item for ProjectDiagnosticsEditor {
self.editor
.update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
}

fn serialized_item_kind() -> Option<&'static str> {
Some("diagnostics")
}

fn deserialize(
project: Model<Project>,
workspace: WeakView<Workspace>,
_workspace_id: workspace::WorkspaceId,
_item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>,
) -> Task<Result<View<Self>>> {
Task::ready(Ok(cx.new_view(|cx| Self::new(project, workspace, cx))))
}
}

const DIAGNOSTIC_HEADER: &'static str = "diagnostic header";
Expand Down
16 changes: 1 addition & 15 deletions crates/diagnostics/src/grouped_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use ui::{h_flex, prelude::*, Icon, IconName, Label};
use util::{debug_panic, ResultExt};
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
ItemNavHistory, ToolbarItemLocation, Workspace,
};

use crate::project_diagnostics_settings::ProjectDiagnosticsSettings;
Expand Down Expand Up @@ -603,20 +603,6 @@ impl Item for GroupedDiagnosticsEditor {
self.editor
.update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
}

fn serialized_item_kind() -> Option<&'static str> {
Some("diagnostics")
}

fn deserialize(
project: Model<Project>,
workspace: WeakView<Workspace>,
_workspace_id: workspace::WorkspaceId,
_item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>,
) -> Task<Result<View<Self>>> {
Task::ready(Ok(cx.new_view(|cx| Self::new(project, workspace, cx))))
}
}

fn compare_data_locations(
Expand Down
12 changes: 10 additions & 2 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ pub fn init(cx: &mut AppContext) {

workspace::register_project_item::<Editor>(cx);
workspace::FollowableViewRegistry::register::<Editor>(cx);
workspace::register_deserializable_item::<Editor>(cx);
workspace::register_serializable_item::<Editor>(cx);

cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(Editor::new_file);
Expand Down Expand Up @@ -550,6 +551,7 @@ pub struct Editor {
show_git_blame_inline: bool,
show_git_blame_inline_delay_task: Option<Task<()>>,
git_blame_inline_enabled: bool,
serialize_dirty_buffers: bool,
show_selection_menu: Option<bool>,
blame: Option<Model<GitBlame>>,
blame_subscription: Option<Subscription>,
Expand Down Expand Up @@ -1876,6 +1878,9 @@ impl Editor {
show_selection_menu: None,
show_git_blame_inline_delay_task: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
serialize_dirty_buffers: ProjectSettings::get_global(cx)
.session
.restore_unsaved_buffers,
blame: None,
blame_subscription: None,
file_header_size,
Expand Down Expand Up @@ -11250,8 +11255,11 @@ impl Editor {
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;

let project_settings = ProjectSettings::get_global(cx);
self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;

if self.mode == EditorMode::Full {
let inline_blame_enabled = ProjectSettings::get_global(cx).git.inline_blame_enabled();
let inline_blame_enabled = project_settings.git.inline_blame_enabled();
if self.git_blame_inline_enabled != inline_blame_enabled {
self.toggle_git_blame_inline_internal(false, cx);
}
Expand Down
Loading

0 comments on commit 9241b11

Please sign in to comment.