diff --git a/gpui/src/app.rs b/gpui/src/app.rs index a8d3e13fde..decc8096be 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -343,6 +343,23 @@ impl TestAppContext { pub fn did_prompt_for_new_path(&self) -> bool { self.1.as_ref().did_prompt_for_new_path() } + + pub fn simulate_prompt_answer(&self, window_id: usize, answer: usize) { + let mut state = self.0.borrow_mut(); + let (_, window) = state + .presenters_and_platform_windows + .get_mut(&window_id) + .unwrap(); + let test_window = window + .as_any_mut() + .downcast_mut::() + .unwrap(); + let callback = test_window + .last_prompt + .take() + .expect("prompt was not called"); + (callback)(answer); + } } impl UpdateModel for TestAppContext { diff --git a/gpui/src/platform/mac/window.rs b/gpui/src/platform/mac/window.rs index 2c6df244e1..b1d86acb12 100644 --- a/gpui/src/platform/mac/window.rs +++ b/gpui/src/platform/mac/window.rs @@ -27,6 +27,7 @@ use objc::{ use pathfinder_geometry::vector::vec2f; use smol::Timer; use std::{ + any::Any, cell::{Cell, RefCell}, convert::TryInto, ffi::c_void, @@ -263,6 +264,10 @@ impl Drop for Window { } impl platform::Window for Window { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn on_event(&mut self, callback: Box) { self.0.as_ref().borrow_mut().event_callback = Some(callback); } diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index f753517449..fd62770c12 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -68,6 +68,7 @@ pub trait Dispatcher: Send + Sync { } pub trait Window: WindowContext { + fn as_any_mut(&mut self) -> &mut dyn Any; fn on_event(&mut self, callback: Box); fn on_resize(&mut self, callback: Box); fn on_close(&mut self, callback: Box); diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index 98c36b110f..d3a0c0d3f1 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -24,6 +24,7 @@ pub struct Window { event_handlers: Vec>, resize_handlers: Vec>, close_handlers: Vec>, + pub(crate) last_prompt: RefCell>>, } impl Platform { @@ -123,6 +124,7 @@ impl Window { close_handlers: Vec::new(), scale_factor: 1.0, current_scene: None, + last_prompt: RefCell::new(None), } } } @@ -152,6 +154,10 @@ impl super::WindowContext for Window { } impl super::Window for Window { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn on_event(&mut self, callback: Box) { self.event_handlers.push(callback); } @@ -164,7 +170,9 @@ impl super::Window for Window { self.close_handlers.push(callback); } - fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str], _: Box) {} + fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str], f: Box) { + self.last_prompt.replace(Some(f)); + } } pub(crate) fn platform() -> Platform { diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index cd299daf9c..b737b0e99d 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -761,7 +761,7 @@ mod tests { use crate::{editor::BufferView, settings, test::temp_tree}; use gpui::App; use serde_json::json; - use std::collections::HashSet; + use std::{collections::HashSet, fs}; use tempdir::TempDir; #[test] @@ -1101,6 +1101,59 @@ mod tests { }); } + #[test] + fn test_save_conflicting_item() { + App::test_async((), |mut app| async move { + let dir = temp_tree(json!({ + "a.txt": "", + })); + + let settings = settings::channel(&app.font_cache()).unwrap().1; + let (window_id, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings, ctx); + workspace.add_worktree(dir.path(), ctx); + workspace + }); + let tree = app.read(|ctx| { + let mut trees = workspace.read(ctx).worktrees().iter(); + trees.next().unwrap().clone() + }); + tree.flush_fs_events(&app).await; + + // Open a file within an existing worktree. + app.update(|ctx| { + workspace.update(ctx, |view, ctx| { + view.open_paths(&[dir.path().join("a.txt")], ctx) + }) + }) + .await; + let editor = app.read(|ctx| { + let pane = workspace.read(ctx).active_pane().read(ctx); + let item = pane.active_item().unwrap(); + item.to_any().downcast::().unwrap() + }); + + app.update(|ctx| { + editor.update(ctx, |editor, ctx| editor.insert(&"x".to_string(), ctx)) + }); + fs::write(dir.path().join("a.txt"), "changed").unwrap(); + tree.flush_fs_events(&app).await; + app.read(|ctx| { + assert!(editor.is_dirty(ctx)); + assert!(editor.has_conflict(ctx)); + }); + + app.update(|ctx| workspace.update(ctx, |w, ctx| w.save_active_item(&(), ctx))); + app.simulate_prompt_answer(window_id, 0); + tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx)) + .await; + app.read(|ctx| { + assert!(!editor.is_dirty(ctx)); + assert!(!editor.has_conflict(ctx)); + }); + }); + } + #[test] fn test_pane_actions() { App::test_async((), |mut app| async move {