Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions crates/tab_switcher/src/tab_switcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use ui::{
};
use util::ResultExt;
use workspace::{
Event as WorkspaceEvent, ModalView, Pane, SaveIntent, Workspace,
AutosaveSetting, Event as WorkspaceEvent, ModalView, Pane, SaveIntent, Workspace,
item::{ItemHandle, ItemSettings, ShowDiagnostics, TabContentParams},
pane::{render_item_indicator, tab_details},
};
Expand Down Expand Up @@ -56,7 +56,11 @@ pub struct TabSwitcher {
init_modifiers: Option<Modifiers>,
}

impl ModalView for TabSwitcher {}
impl ModalView for TabSwitcher {
fn blocks_focus_change_autosave(&self) -> bool {
true
}
}

pub fn init(cx: &mut App) {
cx.observe_new(TabSwitcher::register).detach();
Expand Down Expand Up @@ -136,6 +140,7 @@ impl TabSwitcher {
let weak_workspace = workspace.weak_handle();

let project = workspace.project().clone();
let initially_focused_item = workspace.active_item(cx);
let original_items: Vec<_> = workspace
.panes()
.iter()
Expand All @@ -150,6 +155,7 @@ impl TabSwitcher {
weak_workspace,
is_global,
open_in_active_pane,
initially_focused_item,
window,
cx,
original_items,
Expand Down Expand Up @@ -254,6 +260,7 @@ pub struct TabSwitcherDelegate {
is_all_panes: bool,
open_in_active_pane: bool,
restored_items: bool,
initially_focused_item: Option<Box<dyn ItemHandle>>,
}

impl TabMatch {
Expand Down Expand Up @@ -337,6 +344,7 @@ impl TabSwitcherDelegate {
workspace: WeakEntity<Workspace>,
is_all_panes: bool,
open_in_active_pane: bool,
initially_focused_item: Option<Box<dyn ItemHandle>>,
window: &mut Window,
cx: &mut Context<TabSwitcher>,
original_items: Vec<(Entity<Pane>, usize)>,
Expand All @@ -354,9 +362,29 @@ impl TabSwitcherDelegate {
open_in_active_pane,
original_items,
restored_items: false,
initially_focused_item,
}
}

fn initially_focused_item_to_autosave(
&self,
selected_item_id: EntityId,
cx: &App,
) -> Option<Box<dyn ItemHandle>> {
let item = self
.initially_focused_item
.as_ref()
.filter(|item| item.item_id() != selected_item_id)
.filter(|item| item.workspace_settings(cx).autosave == AutosaveSetting::OnFocusChange)?
.boxed_clone();

Some(item)
}

fn autosave_item(&self, item: &dyn ItemHandle, window: &mut Window, cx: &mut App) {
Pane::autosave_item(item, self.project.clone(), window, cx).detach_and_log_err(cx);
}

fn subscribe_to_updates(
workspace: &WeakEntity<Workspace>,
window: &mut Window,
Expand Down Expand Up @@ -642,6 +670,7 @@ impl TabSwitcherDelegate {
fn confirm_open_in_active_pane(
&mut self,
selected_match: TabMatch,
autosave_item: Option<Box<dyn ItemHandle>>,
window: &mut Window,
cx: &mut Context<Picker<TabSwitcherDelegate>>,
) {
Expand Down Expand Up @@ -672,6 +701,9 @@ impl TabSwitcherDelegate {
current_pane.update(cx, |pane, cx| {
pane.activate_item(index, true, true, window, cx);
});
if let Some(item) = autosave_item.as_ref() {
self.autosave_item(item.as_ref(), window, cx);
}
} else if selected_match.item.project_path(cx).is_some()
&& selected_match.item.can_split(cx)
{
Expand All @@ -681,11 +713,16 @@ impl TabSwitcherDelegate {
let database_id = workspace.read(cx).database_id();
let task = selected_match.item.clone_on_split(database_id, window, cx);
let current_pane = current_pane.downgrade();
let project = self.project.clone();
cx.spawn_in(window, async move |_, cx| {
if let Some(clone) = task.await {
current_pane
.update_in(cx, |pane, window, cx| {
pane.add_item(clone, true, true, None, window, cx);
if let Some(item) = autosave_item.as_ref() {
Pane::autosave_item(item.as_ref(), project.clone(), window, cx)
.detach_and_log_err(cx);
}
})
.log_err();
}
Expand All @@ -704,6 +741,9 @@ impl TabSwitcherDelegate {
window,
cx,
);
if let Some(item) = autosave_item.as_ref() {
self.autosave_item(item.as_ref(), window, cx);
}
}
}
}
Expand Down Expand Up @@ -774,6 +814,8 @@ impl PickerDelegate for TabSwitcherDelegate {
let Some(selected_match) = self.matches.get(self.selected_index()).cloned() else {
return;
};
let selected_item_id = selected_match.item.item_id();
let autosave_item = self.initially_focused_item_to_autosave(selected_item_id, cx);

self.restored_items = true;
for (pane, index) in self.original_items.iter() {
Expand All @@ -783,7 +825,7 @@ impl PickerDelegate for TabSwitcherDelegate {
}

if self.open_in_active_pane {
self.confirm_open_in_active_pane(selected_match, window, cx);
self.confirm_open_in_active_pane(selected_match, autosave_item, window, cx);
} else {
selected_match
.pane
Expand All @@ -793,6 +835,9 @@ impl PickerDelegate for TabSwitcherDelegate {
}
})
.ok();
if let Some(item) = autosave_item.as_ref() {
self.autosave_item(item.as_ref(), window, cx);
}
}
}

Expand Down
67 changes: 65 additions & 2 deletions crates/tab_switcher/src/tab_switcher_tests.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use super::*;
use editor::Editor;
use gpui::{TestAppContext, VisualTestContext};
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
use menu::SelectPrevious;
use project::{Project, ProjectPath};
use serde_json::json;
use settings::SettingsStore;
use util::{path, rel_path::rel_path};
use workspace::{ActivatePreviousItem, AppState, MultiWorkspace, Workspace, item::test::TestItem};
use workspace::{
ActivatePreviousItem, AppState, AutosaveSetting, MultiWorkspace, Workspace,
item::test::{TestItem, TestProjectItem},
};

#[ctor::ctor]
fn init_logger() {
Expand Down Expand Up @@ -191,6 +195,65 @@ async fn test_open_with_single_item(cx: &mut gpui::TestAppContext) {
});
}

#[gpui::test]
async fn test_confirm_switch_autosaves_initial_item_after_switch(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (multi_workspace, cx) =
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());

let first_item = cx.new(|cx| {
TestItem::new(cx)
.with_label("first")
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let second_item = cx.new(|cx| {
TestItem::new(cx)
.with_label("second")
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
});

workspace.update_in(cx, |workspace, window, cx| {
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings(cx, |settings| {
settings.workspace.autosave = Some(AutosaveSetting::OnFocusChange);
})
});

workspace.add_item_to_active_pane(Box::new(first_item.clone()), None, true, window, cx);
workspace.add_item_to_active_pane(Box::new(second_item.clone()), None, true, window, cx);
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_item(0, true, true, window, cx);
});
});
cx.run_until_parked();

open_tab_switcher(false, &workspace, cx);
cx.run_until_parked();

first_item.read_with(cx, |item, _| {
assert_eq!(
item.save_count, 0,
"opening TabSwitcher should not autosave"
);
});

cx.dispatch_action(menu::Confirm);
cx.run_until_parked();

first_item.read_with(cx, |item, _| {
assert_eq!(
item.save_count, 1,
"confirming the switch should autosave the old tab"
);
});
second_item.read_with(cx, |item, _| {
assert_eq!(item.save_count, 0);
});
}

#[gpui::test]
async fn test_close_selected_item(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
Expand Down
2 changes: 1 addition & 1 deletion crates/workspace/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ impl<T: Item> ItemHandle for Entity<T> {
// since the user is still interacting with the workspace.
let focus_handle = item.item_focus_handle(cx);
if !focus_handle.contains_focused(window, cx)
&& !workspace.has_active_modal(window, cx)
&& !workspace.active_modal_blocks_focus_change_autosave(cx)
{
Pane::autosave_item(&item, workspace.project.clone(), window, cx)
.detach_and_log_err(cx);
Expand Down
15 changes: 15 additions & 0 deletions crates/workspace/src/modal_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ pub trait ModalView: ManagedView {
fn render_bare(&self) -> bool {
false
}

fn blocks_focus_change_autosave(&self) -> bool {
true
}
}

trait ModalViewHandle {
fn on_before_dismiss(&mut self, window: &mut Window, cx: &mut App) -> DismissDecision;
fn view(&self) -> AnyView;
fn fade_out_background(&self, cx: &mut App) -> bool;
fn render_bare(&self, cx: &mut App) -> bool;
fn blocks_focus_change_autosave(&self, cx: &App) -> bool;
}

impl<V: ModalView> ModalViewHandle for Entity<V> {
Expand All @@ -51,6 +56,10 @@ impl<V: ModalView> ModalViewHandle for Entity<V> {
fn render_bare(&self, cx: &mut App) -> bool {
self.read(cx).render_bare()
}

fn blocks_focus_change_autosave(&self, cx: &App) -> bool {
self.read(cx).blocks_focus_change_autosave()
}
}

pub struct ActiveModal {
Expand Down Expand Up @@ -189,6 +198,12 @@ impl ModalLayer {
pub fn has_active_modal(&self) -> bool {
self.active_modal.is_some()
}

pub fn active_modal_blocks_focus_change_autosave(&self, cx: &App) -> bool {
self.active_modal
.as_ref()
.is_some_and(|modal| modal.modal.blocks_focus_change_autosave(cx))
}
}

impl Render for ModalLayer {
Expand Down
6 changes: 6 additions & 0 deletions crates/workspace/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6929,6 +6929,12 @@ impl Workspace {
self.modal_layer.read(cx).has_active_modal()
}

pub fn active_modal_blocks_focus_change_autosave(&self, cx: &App) -> bool {
self.modal_layer
.read(cx)
.active_modal_blocks_focus_change_autosave(cx)
}

pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
self.modal_layer.read(cx).active_modal()
}
Expand Down
Loading