Detailed changes
@@ -115,6 +115,15 @@
"confirm_quit": false,
// Whether to restore last closed project when fresh Zed instance is opened.
"restore_on_startup": "last_session",
+ // Whether to attempt to restore previous file's state when opening it again.
+ // The state is stored per pane.
+ // When disabled, defaults are applied instead of the state restoration.
+ //
+ // E.g. for editors, selections, folds and scroll positions are restored, if the same file is closed and, later, opened again in the same pane.
+ // When disabled, a single selection in the very beginning of the file, zero scroll position and no folds state is used as a default.
+ //
+ // Default: true
+ "restore_on_file_reopen": true,
// Size of the drop target in the editor.
"drop_target_size": 0.2,
// Whether the window should be closed when using 'close active item' on a window with no tabs.
@@ -1597,6 +1597,17 @@ impl Editor {
}
this.tasks_update_task = Some(this.refresh_runnables(window, cx));
this._subscriptions.extend(project_subscriptions);
+ this._subscriptions
+ .push(cx.subscribe_self(|editor, e: &EditorEvent, cx| {
+ if let EditorEvent::SelectionsChanged { local } = e {
+ if *local {
+ let new_anchor = editor.scroll_manager.anchor();
+ editor.update_restoration_data(cx, move |data| {
+ data.scroll_anchor = new_anchor;
+ });
+ }
+ }
+ }));
this.end_selection(window, cx);
this.scroll_manager.show_scrollbars(window, cx);
@@ -2317,18 +2328,24 @@ impl Editor {
if selections.len() == 1 {
cx.emit(SearchEvent::ActiveMatchChanged)
}
- if local
- && self.is_singleton(cx)
- && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
- {
- if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) {
- let background_executor = cx.background_executor().clone();
- let editor_id = cx.entity().entity_id().as_u64() as ItemId;
- let snapshot = self.buffer().read(cx).snapshot(cx);
- let selections = selections.clone();
- self.serialize_selections = cx.background_spawn(async move {
+ if local && self.is_singleton(cx) {
+ let inmemory_selections = selections.iter().map(|s| s.range()).collect();
+ self.update_restoration_data(cx, |data| {
+ data.selections = inmemory_selections;
+ });
+
+ if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
+ {
+ if let Some(workspace_id) =
+ self.workspace.as_ref().and_then(|workspace| workspace.1)
+ {
+ let snapshot = self.buffer().read(cx).snapshot(cx);
+ let selections = selections.clone();
+ let background_executor = cx.background_executor().clone();
+ let editor_id = cx.entity().entity_id().as_u64() as ItemId;
+ self.serialize_selections = cx.background_spawn(async move {
background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
- let selections = selections
+ let db_selections = selections
.iter()
.map(|selection| {
(
@@ -2338,11 +2355,12 @@ impl Editor {
})
.collect();
- DB.save_editor_selections(editor_id, workspace_id, selections)
+ DB.save_editor_selections(editor_id, workspace_id, db_selections)
.await
.with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
.log_err();
});
+ }
}
}
@@ -2356,13 +2374,24 @@ impl Editor {
return;
}
+ let snapshot = self.buffer().read(cx).snapshot(cx);
+ let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
+ display_map
+ .snapshot(cx)
+ .folds_in_range(0..snapshot.len())
+ .map(|fold| fold.range.deref().clone())
+ .collect()
+ });
+ self.update_restoration_data(cx, |data| {
+ data.folds = inmemory_folds;
+ });
+
let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
return;
};
let background_executor = cx.background_executor().clone();
let editor_id = cx.entity().entity_id().as_u64() as ItemId;
- let snapshot = self.buffer().read(cx).snapshot(cx);
- let folds = self.display_map.update(cx, |display_map, cx| {
+ let db_folds = self.display_map.update(cx, |display_map, cx| {
display_map
.snapshot(cx)
.folds_in_range(0..snapshot.len())
@@ -2376,7 +2405,7 @@ impl Editor {
});
self.serialize_folds = cx.background_spawn(async move {
background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
- DB.save_editor_folds(editor_id, workspace_id, folds)
+ DB.save_editor_folds(editor_id, workspace_id, db_folds)
.await
.with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
.log_err();
@@ -17454,19 +17483,6 @@ impl Editor {
{
let buffer_snapshot = OnceCell::new();
- if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
- if !selections.is_empty() {
- let snapshot =
- buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
- self.change_selections(None, window, cx, |s| {
- s.select_ranges(selections.into_iter().map(|(start, end)| {
- snapshot.clip_offset(start, Bias::Left)
- ..snapshot.clip_offset(end, Bias::Right)
- }));
- });
- }
- };
-
if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
if !folds.is_empty() {
let snapshot =
@@ -17485,6 +17501,19 @@ impl Editor {
);
}
}
+
+ if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
+ if !selections.is_empty() {
+ let snapshot =
+ buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
+ self.change_selections(None, window, cx, |s| {
+ s.select_ranges(selections.into_iter().map(|(start, end)| {
+ snapshot.clip_offset(start, Bias::Left)
+ ..snapshot.clip_offset(end, Bias::Right)
+ }));
+ });
+ }
+ };
}
self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
@@ -52,7 +52,7 @@ use util::{
};
use workspace::{
item::{FollowEvent, FollowableItem, Item, ItemHandle},
- NavigationEntry, ViewId,
+ CloseAllItems, CloseInactiveItems, NavigationEntry, ViewId,
};
#[gpui::test]
@@ -18284,6 +18284,396 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
});
}
+#[gpui::test]
+async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let fs = FakeFs::new(cx.executor());
+ let main_text = r#"fn main() {
+println!("1");
+println!("2");
+println!("3");
+println!("4");
+println!("5");
+}"#;
+ let lib_text = "mod foo {}";
+ fs.insert_tree(
+ path!("/a"),
+ json!({
+ "lib.rs": lib_text,
+ "main.rs": main_text,
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+ let worktree_id = workspace.update(cx, |workspace, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project.worktrees(cx).next().unwrap().read(cx).id()
+ })
+ });
+
+ let expected_ranges = vec![
+ Point::new(0, 0)..Point::new(0, 0),
+ Point::new(1, 0)..Point::new(1, 1),
+ Point::new(2, 0)..Point::new(2, 2),
+ Point::new(3, 0)..Point::new(3, 3),
+ ];
+
+ let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ let editor_1 = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "main.rs"),
+ Some(pane_1.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ pane_1.update(cx, |pane, cx| {
+ let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
+ open_editor.update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ main_text,
+ "Original main.rs text on initial open",
+ );
+ assert_eq!(
+ editor
+ .selections
+ .all::<Point>(cx)
+ .into_iter()
+ .map(|s| s.range())
+ .collect::<Vec<_>>(),
+ vec![Point::zero()..Point::zero()],
+ "Default selections on initial open",
+ );
+ })
+ });
+ editor_1.update_in(cx, |editor, window, cx| {
+ editor.change_selections(None, window, cx, |s| {
+ s.select_ranges(expected_ranges.clone());
+ });
+ });
+
+ let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
+ workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
+ });
+ let editor_2 = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "main.rs"),
+ Some(pane_2.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ pane_2.update(cx, |pane, cx| {
+ let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
+ open_editor.update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ main_text,
+ "Original main.rs text on initial open in another panel",
+ );
+ assert_eq!(
+ editor
+ .selections
+ .all::<Point>(cx)
+ .into_iter()
+ .map(|s| s.range())
+ .collect::<Vec<_>>(),
+ vec![Point::zero()..Point::zero()],
+ "Default selections on initial open in another panel",
+ );
+ })
+ });
+
+ editor_2.update_in(cx, |editor, window, cx| {
+ editor.fold_ranges(expected_ranges.clone(), false, window, cx);
+ });
+
+ let _other_editor_1 = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "lib.rs"),
+ Some(pane_1.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ pane_1
+ .update_in(cx, |pane, window, cx| {
+ pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
+ .unwrap()
+ })
+ .await
+ .unwrap();
+ drop(editor_1);
+ pane_1.update(cx, |pane, cx| {
+ pane.active_item()
+ .unwrap()
+ .downcast::<Editor>()
+ .unwrap()
+ .update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ lib_text,
+ "Other file should be open and active",
+ );
+ });
+ assert_eq!(pane.items().count(), 1, "No other editors should be open");
+ });
+
+ let _other_editor_2 = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "lib.rs"),
+ Some(pane_2.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ pane_2
+ .update_in(cx, |pane, window, cx| {
+ pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
+ .unwrap()
+ })
+ .await
+ .unwrap();
+ drop(editor_2);
+ pane_2.update(cx, |pane, cx| {
+ let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
+ open_editor.update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ lib_text,
+ "Other file should be open and active in another panel too",
+ );
+ });
+ assert_eq!(
+ pane.items().count(),
+ 1,
+ "No other editors should be open in another pane",
+ );
+ });
+
+ let _editor_1_reopened = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "main.rs"),
+ Some(pane_1.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ let _editor_2_reopened = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "main.rs"),
+ Some(pane_2.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ pane_1.update(cx, |pane, cx| {
+ let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
+ open_editor.update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ main_text,
+ "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
+ );
+ assert_eq!(
+ editor
+ .selections
+ .all::<Point>(cx)
+ .into_iter()
+ .map(|s| s.range())
+ .collect::<Vec<_>>(),
+ expected_ranges,
+ "Previous editor in the 1st panel had selections and should get them restored on reopen",
+ );
+ })
+ });
+ pane_2.update(cx, |pane, cx| {
+ let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
+ open_editor.update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ r#"fn main() {
+β―rintln!("1");
+β―intln!("2");
+β―ntln!("3");
+println!("4");
+println!("5");
+}"#,
+ "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
+ );
+ assert_eq!(
+ editor
+ .selections
+ .all::<Point>(cx)
+ .into_iter()
+ .map(|s| s.range())
+ .collect::<Vec<_>>(),
+ vec![Point::zero()..Point::zero()],
+ "Previous editor in the 2nd pane had no selections changed hence should restore none",
+ );
+ })
+ });
+}
+
+#[gpui::test]
+async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let fs = FakeFs::new(cx.executor());
+ let main_text = r#"fn main() {
+println!("1");
+println!("2");
+println!("3");
+println!("4");
+println!("5");
+}"#;
+ let lib_text = "mod foo {}";
+ fs.insert_tree(
+ path!("/a"),
+ json!({
+ "lib.rs": lib_text,
+ "main.rs": main_text,
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+ let worktree_id = workspace.update(cx, |workspace, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project.worktrees(cx).next().unwrap().read(cx).id()
+ })
+ });
+
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ let editor = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "main.rs"),
+ Some(pane.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ pane.update(cx, |pane, cx| {
+ let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
+ open_editor.update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ main_text,
+ "Original main.rs text on initial open",
+ );
+ })
+ });
+ editor.update_in(cx, |editor, window, cx| {
+ editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
+ });
+
+ cx.update_global(|store: &mut SettingsStore, cx| {
+ store.update_user_settings::<WorkspaceSettings>(cx, |s| {
+ s.restore_on_file_reopen = Some(false);
+ });
+ });
+ editor.update_in(cx, |editor, window, cx| {
+ editor.fold_ranges(
+ vec![
+ Point::new(1, 0)..Point::new(1, 1),
+ Point::new(2, 0)..Point::new(2, 2),
+ Point::new(3, 0)..Point::new(3, 3),
+ ],
+ false,
+ window,
+ cx,
+ );
+ });
+ pane.update_in(cx, |pane, window, cx| {
+ pane.close_all_items(&CloseAllItems::default(), window, cx)
+ .unwrap()
+ })
+ .await
+ .unwrap();
+ pane.update(cx, |pane, _| {
+ assert!(pane.active_item().is_none());
+ });
+ cx.update_global(|store: &mut SettingsStore, cx| {
+ store.update_user_settings::<WorkspaceSettings>(cx, |s| {
+ s.restore_on_file_reopen = Some(true);
+ });
+ });
+
+ let _editor_reopened = workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path(
+ (worktree_id, "main.rs"),
+ Some(pane.downgrade()),
+ true,
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await
+ .downcast::<Editor>()
+ .unwrap();
+ pane.update(cx, |pane, cx| {
+ let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
+ open_editor.update(cx, |editor, cx| {
+ assert_eq!(
+ editor.display_text(cx),
+ main_text,
+ "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
+ );
+ })
+ });
+}
+
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point
@@ -6,7 +6,8 @@ use crate::{
MultiBuffer, MultiBufferSnapshot, NavigationData, SearchWithinRange, ToPoint as _,
};
use anyhow::{anyhow, Context as _, Result};
-use collections::HashSet;
+use clock::Global;
+use collections::{HashMap, HashSet};
use file_icons::FileIcons;
use futures::future::try_join_all;
use git::status::GitSummary;
@@ -21,7 +22,7 @@ use language::{
use lsp::DiagnosticSeverity;
use project::{
lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Project,
- ProjectItem as _, ProjectPath,
+ ProjectEntryId, ProjectItem as _, ProjectPath,
};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
@@ -29,6 +30,7 @@ use std::{
any::TypeId,
borrow::Cow,
cmp::{self, Ordering},
+ collections::hash_map,
iter,
ops::Range,
path::Path,
@@ -39,9 +41,9 @@ use theme::{Theme, ThemeSettings};
use ui::{prelude::*, IconDecorationKind};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::{
- item::{BreadcrumbText, FollowEvent},
+ item::{BreadcrumbText, FollowEvent, ProjectItemKind},
searchable::SearchOptions,
- OpenVisible,
+ OpenVisible, Pane, WorkspaceSettings,
};
use workspace::{
item::{Dedup, ItemSettings, SerializableItem, TabContentParams},
@@ -1250,21 +1252,119 @@ impl SerializableItem for Editor {
}
}
+#[derive(Debug, Default)]
+struct EditorRestorationData {
+ entries: HashMap<ProjectEntryId, RestorationData>,
+}
+
+#[derive(Debug)]
+pub struct RestorationData {
+ pub scroll_anchor: ScrollAnchor,
+ pub folds: Vec<Range<Anchor>>,
+ pub selections: Vec<Range<Anchor>>,
+ pub buffer_version: Global,
+}
+
+impl Default for RestorationData {
+ fn default() -> Self {
+ Self {
+ scroll_anchor: ScrollAnchor::new(),
+ folds: Vec::new(),
+ selections: Vec::new(),
+ buffer_version: Global::default(),
+ }
+ }
+}
+
impl ProjectItem for Editor {
type Item = Buffer;
+ fn project_item_kind() -> Option<ProjectItemKind> {
+ Some(ProjectItemKind("Editor"))
+ }
+
fn for_project_item(
project: Entity<Project>,
+ pane: &Pane,
buffer: Entity<Buffer>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
- Self::for_buffer(buffer, Some(project), window, cx)
+ let mut editor = Self::for_buffer(buffer.clone(), Some(project), window, cx);
+
+ if WorkspaceSettings::get(None, cx).restore_on_file_reopen {
+ if let Some(restoration_data) = Self::project_item_kind()
+ .and_then(|kind| pane.project_item_restoration_data.get(&kind))
+ .and_then(|data| data.downcast_ref::<EditorRestorationData>())
+ .and_then(|data| data.entries.get(&buffer.read(cx).entry_id(cx)?))
+ .filter(|data| !buffer.read(cx).version.changed_since(&data.buffer_version))
+ {
+ editor.fold_ranges(restoration_data.folds.clone(), false, window, cx);
+ if !restoration_data.selections.is_empty() {
+ editor.change_selections(None, window, cx, |s| {
+ s.select_ranges(restoration_data.selections.clone());
+ });
+ }
+ editor.set_scroll_anchor(restoration_data.scroll_anchor, window, cx);
+ }
+ }
+
+ editor
}
}
impl EventEmitter<SearchEvent> for Editor {}
+impl Editor {
+ pub fn update_restoration_data(
+ &self,
+ cx: &mut Context<Self>,
+ write: impl for<'a> FnOnce(&'a mut RestorationData) + 'static,
+ ) {
+ if !WorkspaceSettings::get(None, cx).restore_on_file_reopen {
+ return;
+ }
+
+ let editor = cx.entity();
+ cx.defer(move |cx| {
+ editor.update(cx, |editor, cx| {
+ let kind = Editor::project_item_kind()?;
+ let pane = editor.workspace()?.read(cx).pane_for(&cx.entity())?;
+ let buffer = editor.buffer().read(cx).as_singleton()?;
+ let entry_id = buffer.read(cx).entry_id(cx)?;
+ let buffer_version = buffer.read(cx).version();
+ pane.update(cx, |pane, _| {
+ let data = pane
+ .project_item_restoration_data
+ .entry(kind)
+ .or_insert_with(|| Box::new(EditorRestorationData::default()) as Box<_>);
+ let data = match data.downcast_mut::<EditorRestorationData>() {
+ Some(data) => data,
+ None => {
+ *data = Box::new(EditorRestorationData::default());
+ data.downcast_mut::<EditorRestorationData>()
+ .expect("just written the type downcasted to")
+ }
+ };
+
+ let data = match data.entries.entry(entry_id) {
+ hash_map::Entry::Occupied(o) => {
+ if buffer_version.changed_since(&o.get().buffer_version) {
+ return None;
+ }
+ o.into_mut()
+ }
+ hash_map::Entry::Vacant(v) => v.insert(RestorationData::default()),
+ };
+ write(data);
+ data.buffer_version = buffer_version;
+ Some(())
+ })
+ });
+ });
+ }
+}
+
pub(crate) enum BufferSearchHighlights {}
impl SearchableItem for Editor {
type Match = Range<Anchor>;
@@ -38,7 +38,7 @@ pub struct ScrollAnchor {
}
impl ScrollAnchor {
- fn new() -> Self {
+ pub(super) fn new() -> Self {
Self {
offset: gpui::Point::default(),
anchor: Anchor::min(),
@@ -19,7 +19,7 @@ use ui::prelude::*;
use util::paths::PathExt;
use workspace::{
item::{BreadcrumbText, Item, ProjectItem, SerializableItem, TabContentParams},
- ItemId, ItemSettings, ToolbarItemLocation, Workspace, WorkspaceId,
+ ItemId, ItemSettings, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
};
pub use crate::image_info::*;
@@ -357,6 +357,7 @@ impl ProjectItem for ImageView {
fn for_project_item(
project: Entity<Project>,
+ _: &Pane,
item: Entity<Self::Item>,
_: &mut Window,
cx: &mut Context<Self>,
@@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};
use util::{path, separator};
use workspace::{
item::{Item, ProjectItem},
- register_project_item, AppState,
+ register_project_item, AppState, Pane,
};
#[gpui::test]
@@ -5146,6 +5146,7 @@ impl ProjectItem for TestProjectItemView {
fn for_project_item(
_: Entity<Project>,
+ _: &Pane,
project_item: Entity<Self::Item>,
_: &mut Window,
cx: &mut Context<Self>,
@@ -17,7 +17,7 @@ use project::{Project, ProjectEntryId, ProjectPath};
use ui::{prelude::*, Tooltip};
use workspace::item::{ItemEvent, TabContentParams};
use workspace::searchable::SearchableItemHandle;
-use workspace::{Item, ItemHandle, ProjectItem, ToolbarItemLocation};
+use workspace::{Item, ItemHandle, Pane, ProjectItem, ToolbarItemLocation};
use workspace::{ToolbarItemEvent, ToolbarItemView};
use super::{Cell, CellPosition, RenderableCell};
@@ -825,6 +825,7 @@ impl ProjectItem for NotebookEditor {
fn for_project_item(
project: Entity<Project>,
+ _: &Pane,
item: Entity<Self::Item>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -24,6 +24,7 @@ impl VimTestContext {
git_ui::init(cx);
crate::init(cx);
search::init(cx);
+ workspace::init_settings(cx);
language::init(cx);
editor::init_settings(cx);
project::Project::init_settings(cx);
@@ -1031,11 +1031,19 @@ impl<T: Item> WeakItemHandle for WeakEntity<T> {
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct ProjectItemKind(pub &'static str);
+
pub trait ProjectItem: Item {
type Item: project::ProjectItem;
+ fn project_item_kind() -> Option<ProjectItemKind> {
+ None
+ }
+
fn for_project_item(
project: Entity<Project>,
+ pane: &Pane,
item: Entity<Self::Item>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -1,7 +1,8 @@
use crate::{
item::{
ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
- ShowCloseButton, ShowDiagnostics, TabContentParams, TabTooltipContent, WeakItemHandle,
+ ProjectItemKind, ShowCloseButton, ShowDiagnostics, TabContentParams, TabTooltipContent,
+ WeakItemHandle,
},
move_item,
notifications::NotifyResultExt,
@@ -9,6 +10,7 @@ use crate::{
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenOptions, OpenTerminal, OpenVisible,
SplitDirection, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,
+ WorkspaceItemBuilder,
};
use anyhow::Result;
use collections::{BTreeSet, HashMap, HashSet, VecDeque};
@@ -321,6 +323,8 @@ pub struct Pane {
pinned_tab_count: usize,
diagnostics: HashMap<ProjectPath, DiagnosticSeverity>,
zoom_out_on_close: bool,
+ /// If a certain project item wants to get recreated with specific data, it can persist its data before the recreation here.
+ pub project_item_restoration_data: HashMap<ProjectItemKind, Box<dyn Any + Send>>,
}
pub struct ActivationHistoryEntry {
@@ -526,6 +530,7 @@ impl Pane {
pinned_tab_count: 0,
diagnostics: Default::default(),
zoom_out_on_close: true,
+ project_item_restoration_data: HashMap::default(),
}
}
@@ -859,7 +864,7 @@ impl Pane {
suggested_position: Option<usize>,
window: &mut Window,
cx: &mut Context<Self>,
- build_item: impl FnOnce(&mut Window, &mut Context<Pane>) -> Box<dyn ItemHandle>,
+ build_item: WorkspaceItemBuilder,
) -> Box<dyn ItemHandle> {
let mut existing_item = None;
if let Some(project_entry_id) = project_entry_id {
@@ -896,7 +901,7 @@ impl Pane {
suggested_position
};
- let new_item = build_item(window, cx);
+ let new_item = build_item(self, window, cx);
if allow_preview {
self.set_preview_item_id(Some(new_item.item_id()), cx);
@@ -457,7 +457,8 @@ type ProjectItemOpener = fn(
)
-> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
-type WorkspaceItemBuilder = Box<dyn FnOnce(&mut Window, &mut Context<Pane>) -> Box<dyn ItemHandle>>;
+type WorkspaceItemBuilder =
+ Box<dyn FnOnce(&mut Pane, &mut Window, &mut Context<Pane>) -> Box<dyn ItemHandle>>;
impl Global for ProjectItemOpeners {}
@@ -473,10 +474,13 @@ pub fn register_project_item<I: ProjectItem>(cx: &mut App) {
let project_item = project_item.await?;
let project_entry_id: Option<ProjectEntryId> =
project_item.read_with(cx, project::ProjectItem::entry_id)?;
- let build_workspace_item = Box::new(|window: &mut Window, cx: &mut Context<Pane>| {
- Box::new(cx.new(|cx| I::for_project_item(project, project_item, window, cx)))
- as Box<dyn ItemHandle>
- }) as Box<_>;
+ let build_workspace_item = Box::new(
+ |pane: &mut Pane, window: &mut Window, cx: &mut Context<Pane>| {
+ Box::new(
+ cx.new(|cx| I::for_project_item(project, pane, project_item, window, cx)),
+ ) as Box<dyn ItemHandle>
+ },
+ ) as Box<_>;
Ok((project_entry_id, build_workspace_item))
}))
});
@@ -3060,8 +3064,9 @@ impl Workspace {
return item;
}
- let item =
- cx.new(|cx| T::for_project_item(self.project().clone(), project_item, window, cx));
+ let item = pane.update(cx, |pane, cx| {
+ cx.new(|cx| T::for_project_item(self.project().clone(), pane, project_item, window, cx))
+ });
let item_id = item.item_id();
let mut destination_index = None;
pane.update(cx, |pane, cx| {
@@ -8720,6 +8725,7 @@ mod tests {
fn for_project_item(
_project: Entity<Project>,
+ _pane: &Pane,
_item: Entity<Self::Item>,
_: &mut Window,
cx: &mut Context<Self>,
@@ -8791,6 +8797,7 @@ mod tests {
fn for_project_item(
_project: Entity<Project>,
+ _pane: &Pane,
_item: Entity<Self::Item>,
_: &mut Window,
cx: &mut Context<Self>,
@@ -8834,6 +8841,7 @@ mod tests {
fn for_project_item(
_project: Entity<Project>,
+ _pane: &Pane,
_item: Entity<Self::Item>,
_: &mut Window,
cx: &mut Context<Self>,
@@ -17,6 +17,7 @@ pub struct WorkspaceSettings {
pub show_call_status_icon: bool,
pub autosave: AutosaveSetting,
pub restore_on_startup: RestoreOnStartupBehavior,
+ pub restore_on_file_reopen: bool,
pub drop_target_size: f32,
pub use_system_path_prompts: bool,
pub use_system_prompts: bool,
@@ -134,6 +135,15 @@ pub struct WorkspaceSettingsContent {
/// Values: none, last_workspace, last_session
/// Default: last_session
pub restore_on_startup: Option<RestoreOnStartupBehavior>,
+ /// Whether to attempt to restore previous file's state when opening it again.
+ /// The state is stored per pane.
+ /// When disabled, defaults are applied instead of the state restoration.
+ ///
+ /// E.g. for editors, selections, folds and scroll positions are restored, if the same file is closed and, later, opened again in the same pane.
+ /// When disabled, a single selection in the very beginning of the file, zero scroll position and no folds state is used as a default.
+ ///
+ /// Default: true
+ pub restore_on_file_reopen: Option<bool>,
/// The size of the workspace split drop targets on the outer edges.
/// Given as a fraction that will be multiplied by the smaller dimension of the workspace.
///