Compare commits
2 commits
main
...
on-app-qui
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d2da9108cd | ||
![]() |
155e29f5d9 |
9 changed files with 146 additions and 103 deletions
|
@ -7,6 +7,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::{Arc, atomic::Ordering::SeqCst},
|
sync::{Arc, atomic::Ordering::SeqCst},
|
||||||
|
task::{Poll, Waker},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,6 +92,36 @@ impl AppCell {
|
||||||
}
|
}
|
||||||
Ok(AppRefMut(self.app.try_borrow_mut()?))
|
Ok(AppRefMut(self.app.try_borrow_mut()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(self: &Rc<AppCell>) {
|
||||||
|
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)]
|
#[doc(hidden)]
|
||||||
|
@ -382,39 +413,13 @@ impl App {
|
||||||
platform.on_quit(Box::new({
|
platform.on_quit(Box::new({
|
||||||
let cx = app.clone();
|
let cx = app.clone();
|
||||||
move || {
|
move || {
|
||||||
cx.borrow_mut().shutdown();
|
cx.shutdown();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app
|
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
|
/// Get the id of the current keyboard layout
|
||||||
pub fn keyboard_layout(&self) -> &dyn PlatformKeyboardLayout {
|
pub fn keyboard_layout(&self) -> &dyn PlatformKeyboardLayout {
|
||||||
self.keyboard_layout.as_ref()
|
self.keyboard_layout.as_ref()
|
||||||
|
|
|
@ -167,7 +167,7 @@ impl TestAppContext {
|
||||||
/// public so the macro can call it.
|
/// public so the macro can call it.
|
||||||
pub fn quit(&self) {
|
pub fn quit(&self) {
|
||||||
self.on_quit.borrow_mut().drain(..).for_each(|f| f());
|
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.
|
/// Register cleanup to run when the test ends.
|
||||||
|
|
|
@ -384,10 +384,9 @@ impl BackgroundExecutor {
|
||||||
self.dispatcher.as_test().unwrap().advance_clock(duration)
|
self.dispatcher.as_test().unwrap().advance_clock(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// in tests, run one task.
|
/// docs
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn tick(&self) -> bool {
|
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
|
/// in tests, run all tasks that are ready to run. If after doing so
|
||||||
|
|
|
@ -545,6 +545,7 @@ pub trait PlatformDispatcher: Send + Sync {
|
||||||
fn now(&self) -> Instant {
|
fn now(&self) -> Instant {
|
||||||
Instant::now()
|
Instant::now()
|
||||||
}
|
}
|
||||||
|
fn tick(&self, _: bool) -> bool;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fn as_test(&self) -> Option<&TestDispatcher> {
|
fn as_test(&self) -> Option<&TestDispatcher> {
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
|
|
||||||
use crate::{PlatformDispatcher, TaskLabel};
|
use crate::{PlatformDispatcher, TaskLabel};
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
use block::{Block, ConcreteBlock, RcBlock};
|
||||||
|
use core_foundation::{
|
||||||
|
base::CFTypeRef,
|
||||||
|
runloop::{
|
||||||
|
CFRunLoopRef, CFRunLoopRunInMode, CFRunLoopWakeUp, kCFRunLoopCommonModes,
|
||||||
|
kCFRunLoopDefaultMode,
|
||||||
|
},
|
||||||
|
};
|
||||||
use objc::{
|
use objc::{
|
||||||
class, msg_send,
|
class, msg_send,
|
||||||
runtime::{BOOL, YES},
|
runtime::{BOOL, YES},
|
||||||
|
@ -11,7 +19,9 @@ use objc::{
|
||||||
};
|
};
|
||||||
use parking::{Parker, Unparker};
|
use parking::{Parker, Unparker};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use smol::io::BlockOn;
|
||||||
use std::{
|
use std::{
|
||||||
|
cell::Cell,
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
ptr::{NonNull, addr_of},
|
ptr::{NonNull, addr_of},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -64,11 +74,21 @@ impl PlatformDispatcher for MacDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||||
|
use core_foundation::runloop::CFRunLoopGetMain;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
dispatch_async_f(
|
let mut runnable = Cell::new(Some(runnable));
|
||||||
dispatch_get_main_queue(),
|
let main_run_loop = CFRunLoopGetMain();
|
||||||
runnable.into_raw().as_ptr() as *mut c_void,
|
let block = ConcreteBlock::new(move || {
|
||||||
Some(trampoline),
|
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<Duration>) -> bool {
|
fn park(&self, timeout: Option<Duration>) -> bool {
|
||||||
if let Some(timeout) = timeout {
|
if let Some(timeout) = timeout {
|
||||||
self.parker.lock().park_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 ())) };
|
let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
|
||||||
task.run();
|
task.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: *const c_void);
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
pub fn deprioritize(&self, task_label: TaskLabel) {
|
||||||
self.state
|
self.state
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -267,6 +205,68 @@ impl PlatformDispatcher for TestDispatcher {
|
||||||
state.start_time + state.time
|
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<TaskLabel>) {
|
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
|
||||||
{
|
{
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
|
|
|
@ -136,6 +136,7 @@ impl DapStore {
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
cx.on_app_quit(Self::shutdown_sessions).detach();
|
||||||
let mode = DapStoreMode::Local(LocalDapStore {
|
let mode = DapStoreMode::Local(LocalDapStore {
|
||||||
fs,
|
fs,
|
||||||
environment,
|
environment,
|
||||||
|
|
|
@ -650,12 +650,13 @@ impl HeadlessProject {
|
||||||
cx: AsyncApp,
|
cx: AsyncApp,
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
cx.update(|cx| {
|
// todo!("come back to this")
|
||||||
// TODO: This is a hack, because in a headless project, shutdown isn't executed
|
// cx.update(|cx| {
|
||||||
// when calling quit, but it should be.
|
// // TODO: This is a hack, because in a headless project, shutdown isn't executed
|
||||||
cx.shutdown();
|
// // when calling quit, but it should be.
|
||||||
cx.quit();
|
// cx.shutdown();
|
||||||
})
|
// cx.quit();
|
||||||
|
// })
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
|
5
crates/vim/test_data/test_insert_ctrl_y.json
Normal file
5
crates/vim/test_data/test_insert_ctrl_y.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{"Put":{"state":"hello\nˇ\nworld"}}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"ctrl-y"}
|
||||||
|
{"Key":"ctrl-e"}
|
||||||
|
{"Get":{"state":"hello\nhoˇ\nworld","mode":"Insert"}}
|
Loading…
Add table
Add a link
Reference in a new issue