diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ded7bae316..c372edf10e 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -7,6 +7,7 @@ use std::{ path::{Path, PathBuf}, rc::{Rc, Weak}, sync::{Arc, atomic::Ordering::SeqCst}, + task::{Poll, Waker}, time::Duration, }; @@ -91,6 +92,36 @@ impl AppCell { } Ok(AppRefMut(self.app.try_borrow_mut()?)) } + + pub fn shutdown(self: &Rc) { + let mut futures = Vec::new(); + + let mut cx = self.borrow_mut(); + + for observer in cx.quit_observers.remove(&()) { + futures.push(observer(&mut cx)); + } + + cx.windows.clear(); + cx.window_handles.clear(); + cx.flush_effects(); + let executor = cx.background_executor.clone(); + drop(cx); + + let waker = Waker::noop(); + let mut future_cx = std::task::Context::from_waker(waker); + let futures = futures::future::join_all(futures); + futures::pin_mut!(futures); + let mut start = std::time::Instant::now(); + while start.elapsed() < SHUTDOWN_TIMEOUT { + match futures.as_mut().poll(&mut future_cx) { + Poll::Pending => { + executor.tick(); + } + Poll::Ready(_) => break, + } + } + } } #[doc(hidden)] @@ -382,39 +413,13 @@ impl App { platform.on_quit(Box::new({ let cx = app.clone(); move || { - cx.borrow_mut().shutdown(); + cx.shutdown(); } })); app } - /// Quit the application gracefully. Handlers registered with [`Context::on_app_quit`] - /// will be given 100ms to complete before exiting. - pub fn shutdown(&mut self) { - let mut futures = Vec::new(); - - for observer in self.quit_observers.remove(&()) { - futures.push(observer(self)); - } - - self.windows.clear(); - self.window_handles.clear(); - self.flush_effects(); - self.quitting = true; - - let futures = futures::future::join_all(futures); - if self - .background_executor - .block_with_timeout(SHUTDOWN_TIMEOUT, futures) - .is_err() - { - log::error!("timed out waiting on app_will_quit"); - } - - self.quitting = false; - } - /// Get the id of the current keyboard layout pub fn keyboard_layout(&self) -> &dyn PlatformKeyboardLayout { self.keyboard_layout.as_ref() diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 35e6032671..36f1c73928 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -167,7 +167,7 @@ impl TestAppContext { /// public so the macro can call it. pub fn quit(&self) { self.on_quit.borrow_mut().drain(..).for_each(|f| f()); - self.app.borrow_mut().shutdown(); + self.app.shutdown(); } /// Register cleanup to run when the test ends. diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 273a3ea503..ba65e61b48 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -384,10 +384,9 @@ impl BackgroundExecutor { self.dispatcher.as_test().unwrap().advance_clock(duration) } - /// in tests, run one task. - #[cfg(any(test, feature = "test-support"))] + /// docs pub fn tick(&self) -> bool { - self.dispatcher.as_test().unwrap().tick(false) + self.dispatcher.tick(true) } /// in tests, run all tasks that are ready to run. If after doing so diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b495d70dfd..8d67e24fa5 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -545,6 +545,7 @@ pub trait PlatformDispatcher: Send + Sync { fn now(&self) -> Instant { Instant::now() } + fn tick(&self, _: bool) -> bool; #[cfg(any(test, feature = "test-support"))] fn as_test(&self) -> Option<&TestDispatcher> { diff --git a/crates/gpui/src/platform/mac/dispatcher.rs b/crates/gpui/src/platform/mac/dispatcher.rs index 137295fb91..6590f6bd7b 100644 --- a/crates/gpui/src/platform/mac/dispatcher.rs +++ b/crates/gpui/src/platform/mac/dispatcher.rs @@ -4,6 +4,14 @@ use crate::{PlatformDispatcher, TaskLabel}; use async_task::Runnable; +use block::{Block, ConcreteBlock, RcBlock}; +use core_foundation::{ + base::CFTypeRef, + runloop::{ + CFRunLoopRef, CFRunLoopRunInMode, CFRunLoopWakeUp, kCFRunLoopCommonModes, + kCFRunLoopDefaultMode, + }, +}; use objc::{ class, msg_send, runtime::{BOOL, YES}, @@ -11,7 +19,9 @@ use objc::{ }; use parking::{Parker, Unparker}; use parking_lot::Mutex; +use smol::io::BlockOn; use std::{ + cell::Cell, ffi::c_void, ptr::{NonNull, addr_of}, sync::Arc, @@ -64,11 +74,21 @@ impl PlatformDispatcher for MacDispatcher { } fn dispatch_on_main_thread(&self, runnable: Runnable) { + use core_foundation::runloop::CFRunLoopGetMain; + unsafe { - dispatch_async_f( - dispatch_get_main_queue(), - runnable.into_raw().as_ptr() as *mut c_void, - Some(trampoline), + let mut runnable = Cell::new(Some(runnable)); + let main_run_loop = CFRunLoopGetMain(); + let block = ConcreteBlock::new(move || { + if let Some(runnable) = runnable.take() { + runnable.run(); + } + }) + .copy(); + CFRunLoopPerformBlock( + main_run_loop, + kCFRunLoopDefaultMode as _, + &*block as *const Block<_, _> as _, ); } } @@ -87,6 +107,13 @@ impl PlatformDispatcher for MacDispatcher { } } + fn tick(&self, background_only: bool) -> bool { + unsafe { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0., 0); + } + true + } + fn park(&self, timeout: Option) -> bool { if let Some(timeout) = timeout { self.parker.lock().park_timeout(timeout) @@ -105,3 +132,7 @@ extern "C" fn trampoline(runnable: *mut c_void) { let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) }; task.run(); } + +unsafe extern "C" { + fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: *const c_void); +} diff --git a/crates/gpui/src/platform/test/dispatcher.rs b/crates/gpui/src/platform/test/dispatcher.rs index 16edabfa4b..b612e470bf 100644 --- a/crates/gpui/src/platform/test/dispatcher.rs +++ b/crates/gpui/src/platform/test/dispatcher.rs @@ -122,68 +122,6 @@ impl TestDispatcher { } } - pub fn tick(&self, background_only: bool) -> bool { - let mut state = self.state.lock(); - - while let Some((deadline, _)) = state.delayed.first() { - if *deadline > state.time { - break; - } - let (_, runnable) = state.delayed.remove(0); - state.background.push(runnable); - } - - let foreground_len: usize = if background_only { - 0 - } else { - state - .foreground - .values() - .map(|runnables| runnables.len()) - .sum() - }; - let background_len = state.background.len(); - - let runnable; - let main_thread; - if foreground_len == 0 && background_len == 0 { - let deprioritized_background_len = state.deprioritized_background.len(); - if deprioritized_background_len == 0 { - return false; - } - let ix = state.random.gen_range(0..deprioritized_background_len); - main_thread = false; - runnable = state.deprioritized_background.swap_remove(ix); - } else { - main_thread = state.random.gen_ratio( - foreground_len as u32, - (foreground_len + background_len) as u32, - ); - if main_thread { - let state = &mut *state; - runnable = state - .foreground - .values_mut() - .filter(|runnables| !runnables.is_empty()) - .choose(&mut state.random) - .unwrap() - .pop_front() - .unwrap(); - } else { - let ix = state.random.gen_range(0..background_len); - runnable = state.background.swap_remove(ix); - }; - }; - - let was_main_thread = state.is_main_thread; - state.is_main_thread = main_thread; - drop(state); - runnable.run(); - self.state.lock().is_main_thread = was_main_thread; - - true - } - pub fn deprioritize(&self, task_label: TaskLabel) { self.state .lock() @@ -267,6 +205,68 @@ impl PlatformDispatcher for TestDispatcher { state.start_time + state.time } + fn tick(&self, background_only: bool) -> bool { + let mut state = self.state.lock(); + + while let Some((deadline, _)) = state.delayed.first() { + if *deadline > state.time { + break; + } + let (_, runnable) = state.delayed.remove(0); + state.background.push(runnable); + } + + let foreground_len: usize = if background_only { + 0 + } else { + state + .foreground + .values() + .map(|runnables| runnables.len()) + .sum() + }; + let background_len = state.background.len(); + + let runnable; + let main_thread; + if foreground_len == 0 && background_len == 0 { + let deprioritized_background_len = state.deprioritized_background.len(); + if deprioritized_background_len == 0 { + return false; + } + let ix = state.random.gen_range(0..deprioritized_background_len); + main_thread = false; + runnable = state.deprioritized_background.swap_remove(ix); + } else { + main_thread = state.random.gen_ratio( + foreground_len as u32, + (foreground_len + background_len) as u32, + ); + if main_thread { + let state = &mut *state; + runnable = state + .foreground + .values_mut() + .filter(|runnables| !runnables.is_empty()) + .choose(&mut state.random) + .unwrap() + .pop_front() + .unwrap(); + } else { + let ix = state.random.gen_range(0..background_len); + runnable = state.background.swap_remove(ix); + }; + }; + + let was_main_thread = state.is_main_thread; + state.is_main_thread = main_thread; + drop(state); + runnable.run(); + self.state.lock().is_main_thread = was_main_thread; + + true + } + fn dispatch(&self, runnable: Runnable, label: Option) { { let mut state = self.state.lock(); diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 6f834b5dc0..9e224ebe00 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -136,6 +136,7 @@ impl DapStore { breakpoint_store: Entity, cx: &mut Context, ) -> Self { + cx.on_app_quit(Self::shutdown_sessions).detach(); let mode = DapStoreMode::Local(LocalDapStore { fs, environment, diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index b4d3162641..e8a6ebdb5d 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -650,12 +650,13 @@ impl HeadlessProject { cx: AsyncApp, ) -> Result { cx.spawn(async move |cx| { - cx.update(|cx| { - // TODO: This is a hack, because in a headless project, shutdown isn't executed - // when calling quit, but it should be. - cx.shutdown(); - cx.quit(); - }) + // todo!("come back to this") + // cx.update(|cx| { + // // TODO: This is a hack, because in a headless project, shutdown isn't executed + // // when calling quit, but it should be. + // cx.shutdown(); + // cx.quit(); + // }) }) .detach(); diff --git a/crates/vim/test_data/test_insert_ctrl_y.json b/crates/vim/test_data/test_insert_ctrl_y.json new file mode 100644 index 0000000000..09b707a198 --- /dev/null +++ b/crates/vim/test_data/test_insert_ctrl_y.json @@ -0,0 +1,5 @@ +{"Put":{"state":"hello\nˇ\nworld"}} +{"Key":"i"} +{"Key":"ctrl-y"} +{"Key":"ctrl-e"} +{"Get":{"state":"hello\nhoˇ\nworld","mode":"Insert"}}