Restore editor state on reopen (#27672)
Closes https://github.com/zed-industries/zed/issues/11626 Part of https://github.com/zed-industries/zed/issues/12853 `"restore_on_file_reopen": true` in workspace settings can now be used to enable and disable editor data between file reopens in the same pane: https://github.com/user-attachments/assets/8d938ee1-d854-42a8-bbc3-2a4e4d7d5933 The settings are generic and panes' data store can be extended for further entities, beyond editors. --------------- Impl details: Currently, the project entry IDs seem to be stable across file reopens, unlike BufferIds, so those were used. Originally, the DB data was considered over in-memory one as editors serialize their state anyway, but managing and exposing PaneIds out of the DB is quite tedious and joining the DB data otherwise is not possible. Release Notes: - Started to restore editor state on reopen
This commit is contained in:
parent
bbd1e628f0
commit
e11e7df724
13 changed files with 611 additions and 48 deletions
|
@ -115,6 +115,15 @@
|
||||||
"confirm_quit": false,
|
"confirm_quit": false,
|
||||||
// Whether to restore last closed project when fresh Zed instance is opened.
|
// Whether to restore last closed project when fresh Zed instance is opened.
|
||||||
"restore_on_startup": "last_session",
|
"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.
|
// Size of the drop target in the editor.
|
||||||
"drop_target_size": 0.2,
|
"drop_target_size": 0.2,
|
||||||
// Whether the window should be closed when using 'close active item' on a window with no tabs.
|
// 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.tasks_update_task = Some(this.refresh_runnables(window, cx));
|
||||||
this._subscriptions.extend(project_subscriptions);
|
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.end_selection(window, cx);
|
||||||
this.scroll_manager.show_scrollbars(window, cx);
|
this.scroll_manager.show_scrollbars(window, cx);
|
||||||
|
@ -2317,18 +2328,24 @@ impl Editor {
|
||||||
if selections.len() == 1 {
|
if selections.len() == 1 {
|
||||||
cx.emit(SearchEvent::ActiveMatchChanged)
|
cx.emit(SearchEvent::ActiveMatchChanged)
|
||||||
}
|
}
|
||||||
if local
|
if local && self.is_singleton(cx) {
|
||||||
&& self.is_singleton(cx)
|
let inmemory_selections = selections.iter().map(|s| s.range()).collect();
|
||||||
&& WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
|
self.update_restoration_data(cx, |data| {
|
||||||
{
|
data.selections = inmemory_selections;
|
||||||
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;
|
if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
|
||||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
{
|
||||||
let selections = selections.clone();
|
if let Some(workspace_id) =
|
||||||
self.serialize_selections = cx.background_spawn(async move {
|
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;
|
background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
|
||||||
let selections = selections
|
let db_selections = selections
|
||||||
.iter()
|
.iter()
|
||||||
.map(|selection| {
|
.map(|selection| {
|
||||||
(
|
(
|
||||||
|
@ -2338,11 +2355,12 @@ impl Editor {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
DB.save_editor_selections(editor_id, workspace_id, selections)
|
DB.save_editor_selections(editor_id, workspace_id, db_selections)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
|
.with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
|
||||||
.log_err();
|
.log_err();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2356,13 +2374,24 @@ impl Editor {
|
||||||
return;
|
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 {
|
let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let background_executor = cx.background_executor().clone();
|
let background_executor = cx.background_executor().clone();
|
||||||
let editor_id = cx.entity().entity_id().as_u64() as ItemId;
|
let editor_id = cx.entity().entity_id().as_u64() as ItemId;
|
||||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
let db_folds = self.display_map.update(cx, |display_map, cx| {
|
||||||
let folds = self.display_map.update(cx, |display_map, cx| {
|
|
||||||
display_map
|
display_map
|
||||||
.snapshot(cx)
|
.snapshot(cx)
|
||||||
.folds_in_range(0..snapshot.len())
|
.folds_in_range(0..snapshot.len())
|
||||||
|
@ -2376,7 +2405,7 @@ impl Editor {
|
||||||
});
|
});
|
||||||
self.serialize_folds = cx.background_spawn(async move {
|
self.serialize_folds = cx.background_spawn(async move {
|
||||||
background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
|
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
|
.await
|
||||||
.with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
|
.with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
|
||||||
.log_err();
|
.log_err();
|
||||||
|
@ -17454,19 +17483,6 @@ impl Editor {
|
||||||
{
|
{
|
||||||
let buffer_snapshot = OnceCell::new();
|
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 let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
|
||||||
if !folds.is_empty() {
|
if !folds.is_empty() {
|
||||||
let snapshot =
|
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);
|
self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
|
||||||
|
|
|
@ -52,7 +52,7 @@ use util::{
|
||||||
};
|
};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{FollowEvent, FollowableItem, Item, ItemHandle},
|
item::{FollowEvent, FollowableItem, Item, ItemHandle},
|
||||||
NavigationEntry, ViewId,
|
CloseAllItems, CloseInactiveItems, NavigationEntry, ViewId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[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> {
|
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||||
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
||||||
point..point
|
point..point
|
||||||
|
|
|
@ -6,7 +6,8 @@ use crate::{
|
||||||
MultiBuffer, MultiBufferSnapshot, NavigationData, SearchWithinRange, ToPoint as _,
|
MultiBuffer, MultiBufferSnapshot, NavigationData, SearchWithinRange, ToPoint as _,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use collections::HashSet;
|
use clock::Global;
|
||||||
|
use collections::{HashMap, HashSet};
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use git::status::GitSummary;
|
use git::status::GitSummary;
|
||||||
|
@ -21,7 +22,7 @@ use language::{
|
||||||
use lsp::DiagnosticSeverity;
|
use lsp::DiagnosticSeverity;
|
||||||
use project::{
|
use project::{
|
||||||
lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, 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 rpc::proto::{self, update_view, PeerId};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -29,6 +30,7 @@ use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
collections::hash_map,
|
||||||
iter,
|
iter,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::Path,
|
path::Path,
|
||||||
|
@ -39,9 +41,9 @@ use theme::{Theme, ThemeSettings};
|
||||||
use ui::{prelude::*, IconDecorationKind};
|
use ui::{prelude::*, IconDecorationKind};
|
||||||
use util::{paths::PathExt, ResultExt, TryFutureExt};
|
use util::{paths::PathExt, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, FollowEvent},
|
item::{BreadcrumbText, FollowEvent, ProjectItemKind},
|
||||||
searchable::SearchOptions,
|
searchable::SearchOptions,
|
||||||
OpenVisible,
|
OpenVisible, Pane, WorkspaceSettings,
|
||||||
};
|
};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Dedup, ItemSettings, SerializableItem, TabContentParams},
|
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 {
|
impl ProjectItem for Editor {
|
||||||
type Item = Buffer;
|
type Item = Buffer;
|
||||||
|
|
||||||
|
fn project_item_kind() -> Option<ProjectItemKind> {
|
||||||
|
Some(ProjectItemKind("Editor"))
|
||||||
|
}
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
pane: &Pane,
|
||||||
buffer: Entity<Buffer>,
|
buffer: Entity<Buffer>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> 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 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 {}
|
pub(crate) enum BufferSearchHighlights {}
|
||||||
impl SearchableItem for Editor {
|
impl SearchableItem for Editor {
|
||||||
type Match = Range<Anchor>;
|
type Match = Range<Anchor>;
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub struct ScrollAnchor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollAnchor {
|
impl ScrollAnchor {
|
||||||
fn new() -> Self {
|
pub(super) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
offset: gpui::Point::default(),
|
offset: gpui::Point::default(),
|
||||||
anchor: Anchor::min(),
|
anchor: Anchor::min(),
|
||||||
|
|
|
@ -19,7 +19,7 @@ use ui::prelude::*;
|
||||||
use util::paths::PathExt;
|
use util::paths::PathExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, Item, ProjectItem, SerializableItem, TabContentParams},
|
item::{BreadcrumbText, Item, ProjectItem, SerializableItem, TabContentParams},
|
||||||
ItemId, ItemSettings, ToolbarItemLocation, Workspace, WorkspaceId,
|
ItemId, ItemSettings, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::image_info::*;
|
pub use crate::image_info::*;
|
||||||
|
@ -357,6 +357,7 @@ impl ProjectItem for ImageView {
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
_: &Pane,
|
||||||
item: Entity<Self::Item>,
|
item: Entity<Self::Item>,
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};
|
||||||
use util::{path, separator};
|
use util::{path, separator};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Item, ProjectItem},
|
item::{Item, ProjectItem},
|
||||||
register_project_item, AppState,
|
register_project_item, AppState, Pane,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -5146,6 +5146,7 @@ impl ProjectItem for TestProjectItemView {
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
_: Entity<Project>,
|
_: Entity<Project>,
|
||||||
|
_: &Pane,
|
||||||
project_item: Entity<Self::Item>,
|
project_item: Entity<Self::Item>,
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|
|
@ -17,7 +17,7 @@ use project::{Project, ProjectEntryId, ProjectPath};
|
||||||
use ui::{prelude::*, Tooltip};
|
use ui::{prelude::*, Tooltip};
|
||||||
use workspace::item::{ItemEvent, TabContentParams};
|
use workspace::item::{ItemEvent, TabContentParams};
|
||||||
use workspace::searchable::SearchableItemHandle;
|
use workspace::searchable::SearchableItemHandle;
|
||||||
use workspace::{Item, ItemHandle, ProjectItem, ToolbarItemLocation};
|
use workspace::{Item, ItemHandle, Pane, ProjectItem, ToolbarItemLocation};
|
||||||
use workspace::{ToolbarItemEvent, ToolbarItemView};
|
use workspace::{ToolbarItemEvent, ToolbarItemView};
|
||||||
|
|
||||||
use super::{Cell, CellPosition, RenderableCell};
|
use super::{Cell, CellPosition, RenderableCell};
|
||||||
|
@ -825,6 +825,7 @@ impl ProjectItem for NotebookEditor {
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
_: &Pane,
|
||||||
item: Entity<Self::Item>,
|
item: Entity<Self::Item>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|
|
@ -24,6 +24,7 @@ impl VimTestContext {
|
||||||
git_ui::init(cx);
|
git_ui::init(cx);
|
||||||
crate::init(cx);
|
crate::init(cx);
|
||||||
search::init(cx);
|
search::init(cx);
|
||||||
|
workspace::init_settings(cx);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
editor::init_settings(cx);
|
editor::init_settings(cx);
|
||||||
project::Project::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 {
|
pub trait ProjectItem: Item {
|
||||||
type Item: project::ProjectItem;
|
type Item: project::ProjectItem;
|
||||||
|
|
||||||
|
fn project_item_kind() -> Option<ProjectItemKind> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
pane: &Pane,
|
||||||
item: Entity<Self::Item>,
|
item: Entity<Self::Item>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
item::{
|
item::{
|
||||||
ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
||||||
ShowCloseButton, ShowDiagnostics, TabContentParams, TabTooltipContent, WeakItemHandle,
|
ProjectItemKind, ShowCloseButton, ShowDiagnostics, TabContentParams, TabTooltipContent,
|
||||||
|
WeakItemHandle,
|
||||||
},
|
},
|
||||||
move_item,
|
move_item,
|
||||||
notifications::NotifyResultExt,
|
notifications::NotifyResultExt,
|
||||||
|
@ -9,6 +10,7 @@ use crate::{
|
||||||
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
|
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
|
||||||
CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenOptions, OpenTerminal, OpenVisible,
|
CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenOptions, OpenTerminal, OpenVisible,
|
||||||
SplitDirection, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,
|
SplitDirection, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,
|
||||||
|
WorkspaceItemBuilder,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::{BTreeSet, HashMap, HashSet, VecDeque};
|
use collections::{BTreeSet, HashMap, HashSet, VecDeque};
|
||||||
|
@ -321,6 +323,8 @@ pub struct Pane {
|
||||||
pinned_tab_count: usize,
|
pinned_tab_count: usize,
|
||||||
diagnostics: HashMap<ProjectPath, DiagnosticSeverity>,
|
diagnostics: HashMap<ProjectPath, DiagnosticSeverity>,
|
||||||
zoom_out_on_close: bool,
|
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 {
|
pub struct ActivationHistoryEntry {
|
||||||
|
@ -526,6 +530,7 @@ impl Pane {
|
||||||
pinned_tab_count: 0,
|
pinned_tab_count: 0,
|
||||||
diagnostics: Default::default(),
|
diagnostics: Default::default(),
|
||||||
zoom_out_on_close: true,
|
zoom_out_on_close: true,
|
||||||
|
project_item_restoration_data: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -859,7 +864,7 @@ impl Pane {
|
||||||
suggested_position: Option<usize>,
|
suggested_position: Option<usize>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
build_item: impl FnOnce(&mut Window, &mut Context<Pane>) -> Box<dyn ItemHandle>,
|
build_item: WorkspaceItemBuilder,
|
||||||
) -> Box<dyn ItemHandle> {
|
) -> Box<dyn ItemHandle> {
|
||||||
let mut existing_item = None;
|
let mut existing_item = None;
|
||||||
if let Some(project_entry_id) = project_entry_id {
|
if let Some(project_entry_id) = project_entry_id {
|
||||||
|
@ -896,7 +901,7 @@ impl Pane {
|
||||||
suggested_position
|
suggested_position
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_item = build_item(window, cx);
|
let new_item = build_item(self, window, cx);
|
||||||
|
|
||||||
if allow_preview {
|
if allow_preview {
|
||||||
self.set_preview_item_id(Some(new_item.item_id()), cx);
|
self.set_preview_item_id(Some(new_item.item_id()), cx);
|
||||||
|
|
|
@ -457,7 +457,8 @@ type ProjectItemOpener = fn(
|
||||||
)
|
)
|
||||||
-> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
|
-> 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 {}
|
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_item = project_item.await?;
|
||||||
let project_entry_id: Option<ProjectEntryId> =
|
let project_entry_id: Option<ProjectEntryId> =
|
||||||
project_item.read_with(cx, project::ProjectItem::entry_id)?;
|
project_item.read_with(cx, project::ProjectItem::entry_id)?;
|
||||||
let build_workspace_item = Box::new(|window: &mut Window, cx: &mut Context<Pane>| {
|
let build_workspace_item = Box::new(
|
||||||
Box::new(cx.new(|cx| I::for_project_item(project, project_item, window, cx)))
|
|pane: &mut Pane, window: &mut Window, cx: &mut Context<Pane>| {
|
||||||
as Box<dyn ItemHandle>
|
Box::new(
|
||||||
}) as Box<_>;
|
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))
|
Ok((project_entry_id, build_workspace_item))
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
@ -3060,8 +3064,9 @@ impl Workspace {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item =
|
let item = pane.update(cx, |pane, cx| {
|
||||||
cx.new(|cx| T::for_project_item(self.project().clone(), project_item, window, cx));
|
cx.new(|cx| T::for_project_item(self.project().clone(), pane, project_item, window, cx))
|
||||||
|
});
|
||||||
let item_id = item.item_id();
|
let item_id = item.item_id();
|
||||||
let mut destination_index = None;
|
let mut destination_index = None;
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
|
@ -8720,6 +8725,7 @@ mod tests {
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
_project: Entity<Project>,
|
_project: Entity<Project>,
|
||||||
|
_pane: &Pane,
|
||||||
_item: Entity<Self::Item>,
|
_item: Entity<Self::Item>,
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -8791,6 +8797,7 @@ mod tests {
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
_project: Entity<Project>,
|
_project: Entity<Project>,
|
||||||
|
_pane: &Pane,
|
||||||
_item: Entity<Self::Item>,
|
_item: Entity<Self::Item>,
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -8834,6 +8841,7 @@ mod tests {
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
_project: Entity<Project>,
|
_project: Entity<Project>,
|
||||||
|
_pane: &Pane,
|
||||||
_item: Entity<Self::Item>,
|
_item: Entity<Self::Item>,
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub struct WorkspaceSettings {
|
||||||
pub show_call_status_icon: bool,
|
pub show_call_status_icon: bool,
|
||||||
pub autosave: AutosaveSetting,
|
pub autosave: AutosaveSetting,
|
||||||
pub restore_on_startup: RestoreOnStartupBehavior,
|
pub restore_on_startup: RestoreOnStartupBehavior,
|
||||||
|
pub restore_on_file_reopen: bool,
|
||||||
pub drop_target_size: f32,
|
pub drop_target_size: f32,
|
||||||
pub use_system_path_prompts: bool,
|
pub use_system_path_prompts: bool,
|
||||||
pub use_system_prompts: bool,
|
pub use_system_prompts: bool,
|
||||||
|
@ -134,6 +135,15 @@ pub struct WorkspaceSettingsContent {
|
||||||
/// Values: none, last_workspace, last_session
|
/// Values: none, last_workspace, last_session
|
||||||
/// Default: last_session
|
/// Default: last_session
|
||||||
pub restore_on_startup: Option<RestoreOnStartupBehavior>,
|
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.
|
/// 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.
|
/// Given as a fraction that will be multiplied by the smaller dimension of the workspace.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue