Add documentation to GPUI (#3980)

🍐d with @conrad @mikayla-maki on covering some of the surface area.

Release Notes:

- N/A
This commit is contained in:
Nathan Sobo 2024-01-09 12:10:53 -07:00 committed by GitHub
commit 00c3afd330
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 345 additions and 25 deletions

View file

@ -1015,7 +1015,6 @@ pub mod tests {
.map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10); .unwrap_or(10);
let _test_platform = &cx.test_platform;
let mut tab_size = rng.gen_range(1..=4); let mut tab_size = rng.gen_range(1..=4);
let buffer_start_excerpt_header_height = rng.gen_range(1..=5); let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
let excerpt_header_height = rng.gen_range(1..=5); let excerpt_header_height = rng.gen_range(1..=5);

View file

@ -45,11 +45,13 @@ use util::{
/// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows. /// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows.
/// Strongly consider removing after stabilization. /// Strongly consider removing after stabilization.
#[doc(hidden)]
pub struct AppCell { pub struct AppCell {
app: RefCell<AppContext>, app: RefCell<AppContext>,
} }
impl AppCell { impl AppCell {
#[doc(hidden)]
#[track_caller] #[track_caller]
pub fn borrow(&self) -> AppRef { pub fn borrow(&self) -> AppRef {
if option_env!("TRACK_THREAD_BORROWS").is_some() { if option_env!("TRACK_THREAD_BORROWS").is_some() {
@ -59,6 +61,7 @@ impl AppCell {
AppRef(self.app.borrow()) AppRef(self.app.borrow())
} }
#[doc(hidden)]
#[track_caller] #[track_caller]
pub fn borrow_mut(&self) -> AppRefMut { pub fn borrow_mut(&self) -> AppRefMut {
if option_env!("TRACK_THREAD_BORROWS").is_some() { if option_env!("TRACK_THREAD_BORROWS").is_some() {
@ -69,6 +72,7 @@ impl AppCell {
} }
} }
#[doc(hidden)]
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct AppRef<'a>(Ref<'a, AppContext>); pub struct AppRef<'a>(Ref<'a, AppContext>);
@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> {
} }
} }
#[doc(hidden)]
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct AppRefMut<'a>(RefMut<'a, AppContext>); pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> {
} }
} }
/// A reference to a GPUI application, typically constructed in the `main` function of your app.
/// You won't interact with this type much outside of initial configuration and startup.
pub struct App(Rc<AppCell>); pub struct App(Rc<AppCell>);
/// Represents an application before it is fully launched. Once your app is /// Represents an application before it is fully launched. Once your app is
@ -136,6 +143,8 @@ impl App {
self self
} }
/// Invokes a handler when an already-running application is launched.
/// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock.
pub fn on_reopen<F>(&self, mut callback: F) -> &Self pub fn on_reopen<F>(&self, mut callback: F) -> &Self
where where
F: 'static + FnMut(&mut AppContext), F: 'static + FnMut(&mut AppContext),
@ -149,18 +158,22 @@ impl App {
self self
} }
/// Returns metadata associated with the application
pub fn metadata(&self) -> AppMetadata { pub fn metadata(&self) -> AppMetadata {
self.0.borrow().app_metadata.clone() self.0.borrow().app_metadata.clone()
} }
/// Returns a handle to the [BackgroundExecutor] associated with this app, which can be used to spawn futures in the background.
pub fn background_executor(&self) -> BackgroundExecutor { pub fn background_executor(&self) -> BackgroundExecutor {
self.0.borrow().background_executor.clone() self.0.borrow().background_executor.clone()
} }
/// Returns a handle to the [ForegroundExecutor] associated with this app, which can be used to spawn futures in the foreground.
pub fn foreground_executor(&self) -> ForegroundExecutor { pub fn foreground_executor(&self) -> ForegroundExecutor {
self.0.borrow().foreground_executor.clone() self.0.borrow().foreground_executor.clone()
} }
/// Returns a reference to the [TextSystem] associated with this app.
pub fn text_system(&self) -> Arc<TextSystem> { pub fn text_system(&self) -> Arc<TextSystem> {
self.0.borrow().text_system.clone() self.0.borrow().text_system.clone()
} }
@ -174,12 +187,6 @@ type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>; type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>; type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
// struct FrameConsumer {
// next_frame_callbacks: Vec<FrameCallback>,
// task: Task<()>,
// display_linker
// }
pub struct AppContext { pub struct AppContext {
pub(crate) this: Weak<AppCell>, pub(crate) this: Weak<AppCell>,
pub(crate) platform: Rc<dyn Platform>, pub(crate) platform: Rc<dyn Platform>,
@ -314,10 +321,12 @@ impl AppContext {
} }
} }
/// Gracefully quit the application via the platform's standard routine.
pub fn quit(&mut self) { pub fn quit(&mut self) {
self.platform.quit(); self.platform.quit();
} }
/// Get metadata about the app and platform.
pub fn app_metadata(&self) -> AppMetadata { pub fn app_metadata(&self) -> AppMetadata {
self.app_metadata.clone() self.app_metadata.clone()
} }
@ -340,6 +349,7 @@ impl AppContext {
result result
} }
/// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context.
pub fn observe<W, E>( pub fn observe<W, E>(
&mut self, &mut self,
entity: &E, entity: &E,
@ -355,7 +365,7 @@ impl AppContext {
}) })
} }
pub fn observe_internal<W, E>( pub(crate) fn observe_internal<W, E>(
&mut self, &mut self,
entity: &E, entity: &E,
mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static, mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
@ -380,15 +390,17 @@ impl AppContext {
subscription subscription
} }
pub fn subscribe<T, E, Evt>( /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type.
/// The callback is provided a handle to the emitting entity and a reference to the emitted event.
pub fn subscribe<T, E, Event>(
&mut self, &mut self,
entity: &E, entity: &E,
mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static, mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static,
) -> Subscription ) -> Subscription
where where
T: 'static + EventEmitter<Evt>, T: 'static + EventEmitter<Event>,
E: Entity<T>, E: Entity<T>,
Evt: 'static, Event: 'static,
{ {
self.subscribe_internal(entity, move |entity, event, cx| { self.subscribe_internal(entity, move |entity, event, cx| {
on_event(entity, event, cx); on_event(entity, event, cx);
@ -426,6 +438,9 @@ impl AppContext {
subscription subscription
} }
/// Returns handles to all open windows in the application.
/// Each handle could be downcast to a handle typed for the root view of that window.
/// To find all windows of a given type, you could filter on
pub fn windows(&self) -> Vec<AnyWindowHandle> { pub fn windows(&self) -> Vec<AnyWindowHandle> {
self.windows self.windows
.values() .values()

View file

@ -82,6 +82,7 @@ impl Context for AsyncAppContext {
} }
impl AsyncAppContext { impl AsyncAppContext {
/// Schedules all windows in the application to be redrawn.
pub fn refresh(&mut self) -> Result<()> { pub fn refresh(&mut self) -> Result<()> {
let app = self let app = self
.app .app
@ -92,14 +93,17 @@ impl AsyncAppContext {
Ok(()) Ok(())
} }
/// Get an executor which can be used to spawn futures in the background.
pub fn background_executor(&self) -> &BackgroundExecutor { pub fn background_executor(&self) -> &BackgroundExecutor {
&self.background_executor &self.background_executor
} }
/// Get an executor which can be used to spawn futures in the foreground.
pub fn foreground_executor(&self) -> &ForegroundExecutor { pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor &self.foreground_executor
} }
/// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> { pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
let app = self let app = self
.app .app
@ -109,6 +113,7 @@ impl AsyncAppContext {
Ok(f(&mut lock)) Ok(f(&mut lock))
} }
/// Open a window with the given options based on the root view returned by the given function.
pub fn open_window<V>( pub fn open_window<V>(
&self, &self,
options: crate::WindowOptions, options: crate::WindowOptions,
@ -125,6 +130,7 @@ impl AsyncAppContext {
Ok(lock.open_window(options, build_root_view)) Ok(lock.open_window(options, build_root_view))
} }
/// Schedule a future to be polled in the background.
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R> pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
where where
Fut: Future<Output = R> + 'static, Fut: Future<Output = R> + 'static,

View file

@ -19,7 +19,10 @@ use std::{
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
use collections::HashMap; use collections::HashMap;
slotmap::new_key_type! { pub struct EntityId; } slotmap::new_key_type! {
/// A unique identifier for a model or view across the application.
pub struct EntityId;
}
impl EntityId { impl EntityId {
pub fn as_u64(self) -> u64 { pub fn as_u64(self) -> u64 {

View file

@ -1,3 +1,5 @@
#![deny(missing_docs)]
use crate::{ use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
@ -9,13 +11,19 @@ use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides
/// an implementation of `Context` with additional methods that are useful in tests.
#[derive(Clone)] #[derive(Clone)]
pub struct TestAppContext { pub struct TestAppContext {
#[doc(hidden)]
pub app: Rc<AppCell>, pub app: Rc<AppCell>,
#[doc(hidden)]
pub background_executor: BackgroundExecutor, pub background_executor: BackgroundExecutor,
#[doc(hidden)]
pub foreground_executor: ForegroundExecutor, pub foreground_executor: ForegroundExecutor,
#[doc(hidden)]
pub dispatcher: TestDispatcher, pub dispatcher: TestDispatcher,
pub test_platform: Rc<TestPlatform>, test_platform: Rc<TestPlatform>,
text_system: Arc<TextSystem>, text_system: Arc<TextSystem>,
} }
@ -76,6 +84,7 @@ impl Context for TestAppContext {
} }
impl TestAppContext { impl TestAppContext {
/// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
pub fn new(dispatcher: TestDispatcher) -> Self { pub fn new(dispatcher: TestDispatcher) -> Self {
let arc_dispatcher = Arc::new(dispatcher.clone()); let arc_dispatcher = Arc::new(dispatcher.clone());
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
@ -95,38 +104,47 @@ impl TestAppContext {
} }
} }
/// returns a new `TestAppContext` re-using the same executors to interleave tasks.
pub fn new_app(&self) -> TestAppContext { pub fn new_app(&self) -> TestAppContext {
Self::new(self.dispatcher.clone()) Self::new(self.dispatcher.clone())
} }
/// Simulates quitting the app.
pub fn quit(&self) { pub fn quit(&self) {
self.app.borrow_mut().shutdown(); self.app.borrow_mut().shutdown();
} }
/// Schedules all windows to be redrawn on the next effect cycle.
pub fn refresh(&mut self) -> Result<()> { pub fn refresh(&mut self) -> Result<()> {
let mut app = self.app.borrow_mut(); let mut app = self.app.borrow_mut();
app.refresh(); app.refresh();
Ok(()) Ok(())
} }
/// Returns an executor (for running tasks in the background)
pub fn executor(&self) -> BackgroundExecutor { pub fn executor(&self) -> BackgroundExecutor {
self.background_executor.clone() self.background_executor.clone()
} }
/// Returns an executor (for running tasks on the main thread)
pub fn foreground_executor(&self) -> &ForegroundExecutor { pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor &self.foreground_executor
} }
/// Gives you an `&mut AppContext` for the duration of the closure
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
cx.update(f) cx.update(f)
} }
/// Gives you an `&AppContext` for the duration of the closure
pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R { pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
let cx = self.app.borrow(); let cx = self.app.borrow();
f(&*cx) f(&*cx)
} }
/// Adds a new window. The Window will always be backed by a `TestWindow` which
/// can be retrieved with `self.test_window(handle)`
pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V> pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
where where
F: FnOnce(&mut ViewContext<V>) -> V, F: FnOnce(&mut ViewContext<V>) -> V,
@ -136,12 +154,16 @@ impl TestAppContext {
cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
} }
/// Adds a new window with no content.
pub fn add_empty_window(&mut self) -> AnyWindowHandle { pub fn add_empty_window(&mut self) -> AnyWindowHandle {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
.any_handle .any_handle
} }
/// Adds a new window, and returns its root view and a `VisualTestContext` which can be used
/// as a `WindowContext` for the rest of the test. Typically you would shadow this context with
/// the returned one. `let (view, cx) = cx.add_window_view(...);`
pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext) pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
where where
F: FnOnce(&mut ViewContext<V>) -> V, F: FnOnce(&mut ViewContext<V>) -> V,
@ -156,18 +178,23 @@ impl TestAppContext {
(view, Box::leak(cx)) (view, Box::leak(cx))
} }
/// returns the TextSystem
pub fn text_system(&self) -> &Arc<TextSystem> { pub fn text_system(&self) -> &Arc<TextSystem> {
&self.text_system &self.text_system
} }
/// Simulates writing to the platform clipboard
pub fn write_to_clipboard(&self, item: ClipboardItem) { pub fn write_to_clipboard(&self, item: ClipboardItem) {
self.test_platform.write_to_clipboard(item) self.test_platform.write_to_clipboard(item)
} }
/// Simulates reading from the platform clipboard.
/// This will return the most recent value from `write_to_clipboard`.
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> { pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.test_platform.read_from_clipboard() self.test_platform.read_from_clipboard()
} }
/// Simulates choosing a File in the platform's "Open" dialog.
pub fn simulate_new_path_selection( pub fn simulate_new_path_selection(
&self, &self,
select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>, select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
@ -175,22 +202,27 @@ impl TestAppContext {
self.test_platform.simulate_new_path_selection(select_path); self.test_platform.simulate_new_path_selection(select_path);
} }
/// Simulates clicking a button in an platform-level alert dialog.
pub fn simulate_prompt_answer(&self, button_ix: usize) { pub fn simulate_prompt_answer(&self, button_ix: usize) {
self.test_platform.simulate_prompt_answer(button_ix); self.test_platform.simulate_prompt_answer(button_ix);
} }
/// Returns true if there's an alert dialog open.
pub fn has_pending_prompt(&self) -> bool { pub fn has_pending_prompt(&self) -> bool {
self.test_platform.has_pending_prompt() self.test_platform.has_pending_prompt()
} }
/// Simulates the user resizing the window to the new size.
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) { pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
self.test_window(window_handle).simulate_resize(size); self.test_window(window_handle).simulate_resize(size);
} }
/// Returns all windows open in the test.
pub fn windows(&self) -> Vec<AnyWindowHandle> { pub fn windows(&self) -> Vec<AnyWindowHandle> {
self.app.borrow().windows().clone() self.app.borrow().windows().clone()
} }
/// Run the given task on the main thread.
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R> pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
where where
Fut: Future<Output = R> + 'static, Fut: Future<Output = R> + 'static,
@ -199,16 +231,20 @@ impl TestAppContext {
self.foreground_executor.spawn(f(self.to_async())) self.foreground_executor.spawn(f(self.to_async()))
} }
/// true if the given global is defined
pub fn has_global<G: 'static>(&self) -> bool { pub fn has_global<G: 'static>(&self) -> bool {
let app = self.app.borrow(); let app = self.app.borrow();
app.has_global::<G>() app.has_global::<G>()
} }
/// runs the given closure with a reference to the global
/// panics if `has_global` would return false.
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R { pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
let app = self.app.borrow(); let app = self.app.borrow();
read(app.global(), &app) read(app.global(), &app)
} }
/// runs the given closure with a reference to the global (if set)
pub fn try_read_global<G: 'static, R>( pub fn try_read_global<G: 'static, R>(
&self, &self,
read: impl FnOnce(&G, &AppContext) -> R, read: impl FnOnce(&G, &AppContext) -> R,
@ -217,11 +253,13 @@ impl TestAppContext {
Some(read(lock.try_global()?, &lock)) Some(read(lock.try_global()?, &lock))
} }
/// sets the global in this context.
pub fn set_global<G: 'static>(&mut self, global: G) { pub fn set_global<G: 'static>(&mut self, global: G) {
let mut lock = self.app.borrow_mut(); let mut lock = self.app.borrow_mut();
lock.set_global(global); lock.set_global(global);
} }
/// updates the global in this context. (panics if `has_global` would return false)
pub fn update_global<G: 'static, R>( pub fn update_global<G: 'static, R>(
&mut self, &mut self,
update: impl FnOnce(&mut G, &mut AppContext) -> R, update: impl FnOnce(&mut G, &mut AppContext) -> R,
@ -230,6 +268,8 @@ impl TestAppContext {
lock.update_global(update) lock.update_global(update)
} }
/// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background
/// thread on the current thread in tests.
pub fn to_async(&self) -> AsyncAppContext { pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext { AsyncAppContext {
app: Rc::downgrade(&self.app), app: Rc::downgrade(&self.app),
@ -238,6 +278,7 @@ impl TestAppContext {
} }
} }
/// Simulate dispatching an action to the currently focused node in the window.
pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A) pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
where where
A: Action, A: Action,
@ -251,7 +292,8 @@ impl TestAppContext {
/// simulate_keystrokes takes a space-separated list of keys to type. /// simulate_keystrokes takes a space-separated list of keys to type.
/// cx.simulate_keystrokes("cmd-shift-p b k s p enter") /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
/// will run backspace on the current editor through the command palette. /// in Zed, this will run backspace on the current editor through the command palette.
/// This will also run the background executor until it's parked.
pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) { pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
for keystroke in keystrokes for keystroke in keystrokes
.split(" ") .split(" ")
@ -266,7 +308,8 @@ impl TestAppContext {
/// simulate_input takes a string of text to type. /// simulate_input takes a string of text to type.
/// cx.simulate_input("abc") /// cx.simulate_input("abc")
/// will type abc into your current editor. /// will type abc into your current editor
/// This will also run the background executor until it's parked.
pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) { pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) { for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
self.dispatch_keystroke(window, keystroke.into(), false); self.dispatch_keystroke(window, keystroke.into(), false);
@ -275,6 +318,7 @@ impl TestAppContext {
self.background_executor.run_until_parked() self.background_executor.run_until_parked()
} }
/// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
pub fn dispatch_keystroke( pub fn dispatch_keystroke(
&mut self, &mut self,
window: AnyWindowHandle, window: AnyWindowHandle,
@ -285,6 +329,7 @@ impl TestAppContext {
.simulate_keystroke(keystroke, is_held) .simulate_keystroke(keystroke, is_held)
} }
/// Returns the `TestWindow` backing the given handle.
pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow { pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
self.app self.app
.borrow_mut() .borrow_mut()
@ -299,6 +344,7 @@ impl TestAppContext {
.clone() .clone()
} }
/// Returns a stream of notifications whenever the View or Model is updated.
pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> { pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
let (tx, rx) = futures::channel::mpsc::unbounded(); let (tx, rx) = futures::channel::mpsc::unbounded();
self.update(|cx| { self.update(|cx| {
@ -315,6 +361,7 @@ impl TestAppContext {
rx rx
} }
/// Retuens a stream of events emitted by the given Model.
pub fn events<Evt, T: 'static + EventEmitter<Evt>>( pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
&mut self, &mut self,
entity: &Model<T>, entity: &Model<T>,
@ -333,6 +380,8 @@ impl TestAppContext {
rx rx
} }
/// Runs until the given condition becomes true. (Prefer `run_until_parked` if you
/// don't need to jump in at a specific time).
pub async fn condition<T: 'static>( pub async fn condition<T: 'static>(
&mut self, &mut self,
model: &Model<T>, model: &Model<T>,
@ -362,6 +411,7 @@ impl TestAppContext {
} }
impl<T: Send> Model<T> { impl<T: Send> Model<T> {
/// Block until the next event is emitted by the model, then return it.
pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
where where
Evt: Send + Clone + 'static, Evt: Send + Clone + 'static,
@ -391,6 +441,7 @@ impl<T: Send> Model<T> {
} }
impl<V: 'static> View<V> { impl<V: 'static> View<V> {
/// Returns a future that resolves when the view is next updated.
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> { pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
use postage::prelude::{Sink as _, Stream as _}; use postage::prelude::{Sink as _, Stream as _};
@ -417,6 +468,7 @@ impl<V: 'static> View<V> {
} }
impl<V> View<V> { impl<V> View<V> {
/// Returns a future that resolves when the condition becomes true.
pub fn condition<Evt>( pub fn condition<Evt>(
&self, &self,
cx: &TestAppContext, cx: &TestAppContext,
@ -483,6 +535,8 @@ impl<V> View<V> {
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
#[derive(Deref, DerefMut, Clone)] #[derive(Deref, DerefMut, Clone)]
/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
/// run window-specific test code.
pub struct VisualTestContext { pub struct VisualTestContext {
#[deref] #[deref]
#[deref_mut] #[deref_mut]
@ -491,10 +545,14 @@ pub struct VisualTestContext {
} }
impl<'a> VisualTestContext { impl<'a> VisualTestContext {
/// Provides the `WindowContext` for the duration of the closure.
pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R { pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
} }
/// Create a new VisualTestContext. You would typically shadow the passed in
/// TestAppContext with this, as this is typically more useful.
/// `let cx = VisualTestContext::from_window(window, cx);`
pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self { pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
Self { Self {
cx: cx.clone(), cx: cx.clone(),
@ -502,10 +560,12 @@ impl<'a> VisualTestContext {
} }
} }
/// Wait until there are no more pending tasks.
pub fn run_until_parked(&self) { pub fn run_until_parked(&self) {
self.cx.background_executor.run_until_parked(); self.cx.background_executor.run_until_parked();
} }
/// Dispatch the action to the currently focused node.
pub fn dispatch_action<A>(&mut self, action: A) pub fn dispatch_action<A>(&mut self, action: A)
where where
A: Action, A: Action,
@ -513,24 +573,32 @@ impl<'a> VisualTestContext {
self.cx.dispatch_action(self.window, action) self.cx.dispatch_action(self.window, action)
} }
/// Read the title off the window (set by `WindowContext#set_window_title`)
pub fn window_title(&mut self) -> Option<String> { pub fn window_title(&mut self) -> Option<String> {
self.cx.test_window(self.window).0.lock().title.clone() self.cx.test_window(self.window).0.lock().title.clone()
} }
/// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
/// Automatically runs until parked.
pub fn simulate_keystrokes(&mut self, keystrokes: &str) { pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
self.cx.simulate_keystrokes(self.window, keystrokes) self.cx.simulate_keystrokes(self.window, keystrokes)
} }
/// Simulate typing text `cx.simulate_input("hello")`
/// Automatically runs until parked.
pub fn simulate_input(&mut self, input: &str) { pub fn simulate_input(&mut self, input: &str) {
self.cx.simulate_input(self.window, input) self.cx.simulate_input(self.window, input)
} }
/// Simulates the user blurring the window.
pub fn deactivate_window(&mut self) { pub fn deactivate_window(&mut self) {
if Some(self.window) == self.test_platform.active_window() { if Some(self.window) == self.test_platform.active_window() {
self.test_platform.set_active_window(None) self.test_platform.set_active_window(None)
} }
self.background_executor.run_until_parked(); self.background_executor.run_until_parked();
} }
/// Simulates the user closing the window.
/// Returns true if the window was closed. /// Returns true if the window was closed.
pub fn simulate_close(&mut self) -> bool { pub fn simulate_close(&mut self) -> bool {
let handler = self let handler = self
@ -667,6 +735,7 @@ impl VisualContext for VisualTestContext {
} }
impl AnyWindowHandle { impl AnyWindowHandle {
/// Creates the given view in this window.
pub fn build_view<V: Render + 'static>( pub fn build_view<V: Render + 'static>(
&self, &self,
cx: &mut TestAppContext, cx: &mut TestAppContext,
@ -676,6 +745,7 @@ impl AnyWindowHandle {
} }
} }
/// An EmptyView for testing.
pub struct EmptyView {} pub struct EmptyView {}
impl Render for EmptyView { impl Render for EmptyView {

View file

@ -32,6 +32,10 @@ pub struct ForegroundExecutor {
not_send: PhantomData<Rc<()>>, not_send: PhantomData<Rc<()>>,
} }
/// Task is a primitive that allows work to happen in the background.
/// It implements Future so you can `.await` on it.
/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows
/// the task to continue running in the background, but with no way to return a value.
#[must_use] #[must_use]
#[derive(Debug)] #[derive(Debug)]
pub enum Task<T> { pub enum Task<T> {
@ -40,10 +44,12 @@ pub enum Task<T> {
} }
impl<T> Task<T> { impl<T> Task<T> {
/// Create a new task that will resolve with the value
pub fn ready(val: T) -> Self { pub fn ready(val: T) -> Self {
Task::Ready(Some(val)) Task::Ready(Some(val))
} }
/// Detaching a task runs it to completion in the background
pub fn detach(self) { pub fn detach(self) {
match self { match self {
Task::Ready(_) => {} Task::Ready(_) => {}
@ -57,6 +63,8 @@ where
T: 'static, T: 'static,
E: 'static + Debug, E: 'static + Debug,
{ {
/// Run the task to completion in the background and log any
/// errors that occur.
#[track_caller] #[track_caller]
pub fn detach_and_log_err(self, cx: &mut AppContext) { pub fn detach_and_log_err(self, cx: &mut AppContext) {
let location = core::panic::Location::caller(); let location = core::panic::Location::caller();
@ -97,6 +105,10 @@ type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>; type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
/// BackgroundExecutor lets you run things on background threads.
/// In production this is a thread pool with no ordering guarantees.
/// In tests this is simalated by running tasks one by one in a deterministic
/// (but arbitrary) order controlled by the `SEED` environment variable.
impl BackgroundExecutor { impl BackgroundExecutor {
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self { pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher } Self { dispatcher }
@ -135,6 +147,7 @@ impl BackgroundExecutor {
Task::Spawned(task) Task::Spawned(task)
} }
/// Used by the test harness to run an async test in a syncronous fashion.
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
#[track_caller] #[track_caller]
pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R { pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
@ -145,6 +158,8 @@ impl BackgroundExecutor {
} }
} }
/// Block the current thread until the given future resolves.
/// Consider using `block_with_timeout` instead.
pub fn block<R>(&self, future: impl Future<Output = R>) -> R { pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
if let Ok(value) = self.block_internal(true, future, usize::MAX) { if let Ok(value) = self.block_internal(true, future, usize::MAX) {
value value
@ -206,6 +221,8 @@ impl BackgroundExecutor {
} }
} }
/// Block the current thread until the given future resolves
/// or `duration` has elapsed.
pub fn block_with_timeout<R>( pub fn block_with_timeout<R>(
&self, &self,
duration: Duration, duration: Duration,
@ -238,6 +255,8 @@ impl BackgroundExecutor {
} }
} }
/// Scoped lets you start a number of tasks and waits
/// for all of them to complete before returning.
pub async fn scoped<'scope, F>(&self, scheduler: F) pub async fn scoped<'scope, F>(&self, scheduler: F)
where where
F: FnOnce(&mut Scope<'scope>), F: FnOnce(&mut Scope<'scope>),
@ -253,6 +272,9 @@ impl BackgroundExecutor {
} }
} }
/// Returns a task that will complete after the given duration.
/// Depending on other concurrent tasks the elapsed duration may be longer
/// than reqested.
pub fn timer(&self, duration: Duration) -> Task<()> { pub fn timer(&self, duration: Duration) -> Task<()> {
let (runnable, task) = async_task::spawn(async move {}, { let (runnable, task) = async_task::spawn(async move {}, {
let dispatcher = self.dispatcher.clone(); let dispatcher = self.dispatcher.clone();
@ -262,65 +284,81 @@ impl BackgroundExecutor {
Task::Spawned(task) Task::Spawned(task)
} }
/// in tests, start_waiting lets you indicate which task is waiting (for debugging only)
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn start_waiting(&self) { pub fn start_waiting(&self) {
self.dispatcher.as_test().unwrap().start_waiting(); self.dispatcher.as_test().unwrap().start_waiting();
} }
/// in tests, removes the debugging data added by start_waiting
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn finish_waiting(&self) { pub fn finish_waiting(&self) {
self.dispatcher.as_test().unwrap().finish_waiting(); self.dispatcher.as_test().unwrap().finish_waiting();
} }
/// in tests, run an arbitrary number of tasks (determined by the SEED environment variable)
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> { pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
self.dispatcher.as_test().unwrap().simulate_random_delay() self.dispatcher.as_test().unwrap().simulate_random_delay()
} }
/// in tests, indicate that a given task from `spawn_labeled` should run after everything else
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn deprioritize(&self, task_label: TaskLabel) { pub fn deprioritize(&self, task_label: TaskLabel) {
self.dispatcher.as_test().unwrap().deprioritize(task_label) self.dispatcher.as_test().unwrap().deprioritize(task_label)
} }
/// in tests, move time forward. This does not run any tasks, but does make `timer`s ready.
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn advance_clock(&self, duration: Duration) { pub fn advance_clock(&self, duration: Duration) {
self.dispatcher.as_test().unwrap().advance_clock(duration) self.dispatcher.as_test().unwrap().advance_clock(duration)
} }
/// in tests, run one task.
#[cfg(any(test, feature = "test-support"))] #[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.as_test().unwrap().tick(false)
} }
/// in tests, run all tasks that are ready to run. If after doing so
/// the test still has outstanding tasks, this will panic. (See also `allow_parking`)
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn run_until_parked(&self) { pub fn run_until_parked(&self) {
self.dispatcher.as_test().unwrap().run_until_parked() self.dispatcher.as_test().unwrap().run_until_parked()
} }
/// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks.
/// This is useful when you are integrating other (non-GPUI) futures, like disk access, that
/// do take real async time to run.
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn allow_parking(&self) { pub fn allow_parking(&self) {
self.dispatcher.as_test().unwrap().allow_parking(); self.dispatcher.as_test().unwrap().allow_parking();
} }
/// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn rng(&self) -> StdRng { pub fn rng(&self) -> StdRng {
self.dispatcher.as_test().unwrap().rng() self.dispatcher.as_test().unwrap().rng()
} }
/// How many CPUs are available to the dispatcher
pub fn num_cpus(&self) -> usize { pub fn num_cpus(&self) -> usize {
num_cpus::get() num_cpus::get()
} }
/// Whether we're on the main thread.
pub fn is_main_thread(&self) -> bool { pub fn is_main_thread(&self) -> bool {
self.dispatcher.is_main_thread() self.dispatcher.is_main_thread()
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
/// in tests, control the number of ticks that `block_with_timeout` will run before timing out.
pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) { pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
self.dispatcher.as_test().unwrap().set_block_on_ticks(range); self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
} }
} }
/// ForegroundExecutor runs things on the main thread.
impl ForegroundExecutor { impl ForegroundExecutor {
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self { pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { Self {
@ -329,8 +367,7 @@ impl ForegroundExecutor {
} }
} }
/// Enqueues the given closure to be run on any thread. The closure returns /// Enqueues the given Task to run on the main thread at some point in the future.
/// a future which will be run to completion on any available thread.
pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R> pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
where where
R: 'static, R: 'static,
@ -350,6 +387,7 @@ impl ForegroundExecutor {
} }
} }
/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped`
pub struct Scope<'a> { pub struct Scope<'a> {
executor: BackgroundExecutor, executor: BackgroundExecutor,
futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>, futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,

View file

@ -15,6 +15,7 @@ use std::{
time::Duration, time::Duration,
}; };
/// TestPlatform implements the Platform trait for use in tests.
pub struct TestPlatform { pub struct TestPlatform {
background_executor: BackgroundExecutor, background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor, foreground_executor: ForegroundExecutor,

View file

@ -1,3 +1,30 @@
//! Test support for GPUI.
//!
//! GPUI provides first-class support for testing, which includes a macro to run test that rely on having a context,
//! and a test implementation of the `ForegroundExecutor` and `BackgroundExecutor` which ensure that your tests run
//! deterministically even in the face of arbitrary parallelism.
//!
//! The output of the `gpui::test` macro is understood by other rust test runners, so you can use it with `cargo test`
//! or `cargo-nextest`, or another runner of your choice.
//!
//! To make it possible to test collaborative user interfaces (like Zed) you can ask for as many different contexts
//! as you need.
//!
//! ## Example
//!
//! ```
//! use gpui;
//!
//! #[gpui::test]
//! async fn test_example(cx: &TestAppContext) {
//! assert!(true)
//! }
//!
//! #[gpui::test]
//! async fn test_collaboration_example(cx_a: &TestAppContext, cx_b: &TestAppContext) {
//! assert!(true)
//! }
//! ```
use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
use futures::StreamExt as _; use futures::StreamExt as _;
use rand::prelude::*; use rand::prelude::*;
@ -68,6 +95,7 @@ impl<T: 'static> futures::Stream for Observation<T> {
} }
} }
/// observe returns a stream of the change events from the given `View` or `Model`
pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> { pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
let (tx, rx) = smol::channel::unbounded(); let (tx, rx) = smol::channel::unbounded();
let _subscription = cx.update(|cx| { let _subscription = cx.update(|cx| {

View file

@ -1,3 +1,5 @@
#![deny(missing_docs)]
use crate::{ use crate::{
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef,
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
@ -85,10 +87,12 @@ pub enum DispatchPhase {
} }
impl DispatchPhase { impl DispatchPhase {
/// Returns true if this represents the "bubble" phase.
pub fn bubble(self) -> bool { pub fn bubble(self) -> bool {
self == DispatchPhase::Bubble self == DispatchPhase::Bubble
} }
/// Returns true if this represents the "capture" phase.
pub fn capture(self) -> bool { pub fn capture(self) -> bool {
self == DispatchPhase::Capture self == DispatchPhase::Capture
} }
@ -103,7 +107,10 @@ struct FocusEvent {
current_focus_path: SmallVec<[FocusId; 8]>, current_focus_path: SmallVec<[FocusId; 8]>,
} }
slotmap::new_key_type! { pub struct FocusId; } slotmap::new_key_type! {
/// A globally unique identifier for a focusable element.
pub struct FocusId;
}
thread_local! { thread_local! {
pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(4 * 1024 * 1024)); pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(4 * 1024 * 1024));
@ -231,6 +238,7 @@ impl Drop for FocusHandle {
/// FocusableView allows users of your view to easily /// FocusableView allows users of your view to easily
/// focus it (using cx.focus_view(view)) /// focus it (using cx.focus_view(view))
pub trait FocusableView: 'static + Render { pub trait FocusableView: 'static + Render {
/// Returns the focus handle associated with this view.
fn focus_handle(&self, cx: &AppContext) -> FocusHandle; fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
} }
@ -240,9 +248,11 @@ pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {}
impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {} impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal.
pub struct DismissEvent; pub struct DismissEvent;
// Holds the state for a specific window. // Holds the state for a specific window.
#[doc(hidden)]
pub struct Window { pub struct Window {
pub(crate) handle: AnyWindowHandle, pub(crate) handle: AnyWindowHandle,
pub(crate) removed: bool, pub(crate) removed: bool,
@ -434,6 +444,7 @@ impl Window {
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
#[repr(C)] #[repr(C)]
pub struct ContentMask<P: Clone + Default + Debug> { pub struct ContentMask<P: Clone + Default + Debug> {
/// The bounds
pub bounds: Bounds<P>, pub bounds: Bounds<P>,
} }
@ -525,11 +536,13 @@ impl<'a> WindowContext<'a> {
self.notify(); self.notify();
} }
/// Blur the window and don't allow anything in it to be focused again.
pub fn disable_focus(&mut self) { pub fn disable_focus(&mut self) {
self.blur(); self.blur();
self.window.focus_enabled = false; self.window.focus_enabled = false;
} }
/// Dispatch the given action on the currently focused element.
pub fn dispatch_action(&mut self, action: Box<dyn Action>) { pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
let focus_handle = self.focused(); let focus_handle = self.focused();
@ -591,6 +604,9 @@ impl<'a> WindowContext<'a> {
}); });
} }
/// Subscribe to events emitted by a model or view.
/// The entity to which you're subscribing must implement the [EventEmitter] trait.
/// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window.
pub fn subscribe<Emitter, E, Evt>( pub fn subscribe<Emitter, E, Evt>(
&mut self, &mut self,
entity: &E, entity: &E,
@ -754,6 +770,9 @@ impl<'a> WindowContext<'a> {
.request_measured_layout(style, rem_size, measure) .request_measured_layout(style, rem_size, measure)
} }
/// Compute the layout for the given id within the given available space.
/// This method is called for its side effect, typically by the framework prior to painting.
/// After calling it, you can request the bounds of the given layout node id or any descendant.
pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) { pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
let mut layout_engine = self.window.layout_engine.take().unwrap(); let mut layout_engine = self.window.layout_engine.take().unwrap();
layout_engine.compute_layout(layout_id, available_space, self); layout_engine.compute_layout(layout_id, available_space, self);
@ -788,30 +807,37 @@ impl<'a> WindowContext<'a> {
.retain(&(), |callback| callback(self)); .retain(&(), |callback| callback(self));
} }
/// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays.
pub fn window_bounds(&self) -> WindowBounds { pub fn window_bounds(&self) -> WindowBounds {
self.window.bounds self.window.bounds
} }
/// Returns the size of the drawable area within the window.
pub fn viewport_size(&self) -> Size<Pixels> { pub fn viewport_size(&self) -> Size<Pixels> {
self.window.viewport_size self.window.viewport_size
} }
/// Returns whether this window is focused by the operating system (receiving key events).
pub fn is_window_active(&self) -> bool { pub fn is_window_active(&self) -> bool {
self.window.active self.window.active
} }
/// Toggle zoom on the window.
pub fn zoom_window(&self) { pub fn zoom_window(&self) {
self.window.platform_window.zoom(); self.window.platform_window.zoom();
} }
/// Update the window's title at the platform level.
pub fn set_window_title(&mut self, title: &str) { pub fn set_window_title(&mut self, title: &str) {
self.window.platform_window.set_title(title); self.window.platform_window.set_title(title);
} }
/// Mark the window as dirty at the platform level.
pub fn set_window_edited(&mut self, edited: bool) { pub fn set_window_edited(&mut self, edited: bool) {
self.window.platform_window.set_edited(edited); self.window.platform_window.set_edited(edited);
} }
/// Determine the display on which the window is visible.
pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> { pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.platform self.platform
.displays() .displays()
@ -819,6 +845,7 @@ impl<'a> WindowContext<'a> {
.find(|display| display.id() == self.window.display_id) .find(|display| display.id() == self.window.display_id)
} }
/// Show the platform character palette.
pub fn show_character_palette(&self) { pub fn show_character_palette(&self) {
self.window.platform_window.show_character_palette(); self.window.platform_window.show_character_palette();
} }
@ -936,6 +963,7 @@ impl<'a> WindowContext<'a> {
.on_action(action_type, ArenaRef::from(listener)); .on_action(action_type, ArenaRef::from(listener));
} }
/// Determine whether the given action is available along the dispatch path to the currently focused element.
pub fn is_action_available(&self, action: &dyn Action) -> bool { pub fn is_action_available(&self, action: &dyn Action) -> bool {
let target = self let target = self
.focused() .focused()
@ -962,6 +990,7 @@ impl<'a> WindowContext<'a> {
self.window.modifiers self.window.modifiers
} }
/// Update the cursor style at the platform level.
pub fn set_cursor_style(&mut self, style: CursorStyle) { pub fn set_cursor_style(&mut self, style: CursorStyle) {
self.window.requested_cursor_style = Some(style) self.window.requested_cursor_style = Some(style)
} }
@ -991,7 +1020,7 @@ impl<'a> WindowContext<'a> {
true true
} }
pub fn was_top_layer_under_active_drag( pub(crate) fn was_top_layer_under_active_drag(
&self, &self,
point: &Point<Pixels>, point: &Point<Pixels>,
level: &StackingOrder, level: &StackingOrder,
@ -1649,6 +1678,7 @@ impl<'a> WindowContext<'a> {
self.dispatch_keystroke_observers(event, None); self.dispatch_keystroke_observers(event, None);
} }
/// Determine whether a potential multi-stroke key binding is in progress on this window.
pub fn has_pending_keystrokes(&self) -> bool { pub fn has_pending_keystrokes(&self) -> bool {
self.window self.window
.rendered_frame .rendered_frame
@ -1715,27 +1745,34 @@ impl<'a> WindowContext<'a> {
subscription subscription
} }
/// Focus the current window and bring it to the foreground at the platform level.
pub fn activate_window(&self) { pub fn activate_window(&self) {
self.window.platform_window.activate(); self.window.platform_window.activate();
} }
/// Minimize the current window at the platform level.
pub fn minimize_window(&self) { pub fn minimize_window(&self) {
self.window.platform_window.minimize(); self.window.platform_window.minimize();
} }
/// Toggle full screen status on the current window at the platform level.
pub fn toggle_full_screen(&self) { pub fn toggle_full_screen(&self) {
self.window.platform_window.toggle_full_screen(); self.window.platform_window.toggle_full_screen();
} }
/// Present a platform dialog.
/// The provided message will be presented, along with buttons for each answer.
/// When a button is clicked, the returned Receiver will receive the index of the clicked button.
pub fn prompt( pub fn prompt(
&self, &self,
level: PromptLevel, level: PromptLevel,
msg: &str, message: &str,
answers: &[&str], answers: &[&str],
) -> oneshot::Receiver<usize> { ) -> oneshot::Receiver<usize> {
self.window.platform_window.prompt(level, msg, answers) self.window.platform_window.prompt(level, message, answers)
} }
/// Returns all available actions for the focused element.
pub fn available_actions(&self) -> Vec<Box<dyn Action>> { pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
let node_id = self let node_id = self
.window .window
@ -1754,6 +1791,7 @@ impl<'a> WindowContext<'a> {
.available_actions(node_id) .available_actions(node_id)
} }
/// Returns key bindings that invoke the given action on the currently focused element.
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> { pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
self.window self.window
.rendered_frame .rendered_frame
@ -1764,6 +1802,7 @@ impl<'a> WindowContext<'a> {
) )
} }
/// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
pub fn bindings_for_action_in( pub fn bindings_for_action_in(
&self, &self,
action: &dyn Action, action: &dyn Action,
@ -1782,6 +1821,7 @@ impl<'a> WindowContext<'a> {
dispatch_tree.bindings_for_action(action, &context_stack) dispatch_tree.bindings_for_action(action, &context_stack)
} }
/// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
pub fn listener_for<V: Render, E>( pub fn listener_for<V: Render, E>(
&self, &self,
view: &View<V>, view: &View<V>,
@ -1793,6 +1833,7 @@ impl<'a> WindowContext<'a> {
} }
} }
/// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle.
pub fn handler_for<V: Render>( pub fn handler_for<V: Render>(
&self, &self,
view: &View<V>, view: &View<V>,
@ -1804,7 +1845,8 @@ impl<'a> WindowContext<'a> {
} }
} }
//========== ELEMENT RELATED FUNCTIONS =========== /// Invoke the given function with the given focus handle present on the key dispatch stack.
/// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint.
pub fn with_key_dispatch<R>( pub fn with_key_dispatch<R>(
&mut self, &mut self,
context: Option<KeyContext>, context: Option<KeyContext>,
@ -1843,6 +1885,8 @@ impl<'a> WindowContext<'a> {
} }
} }
/// Register a callback that can interrupt the closing of the current window based the returned boolean.
/// If the callback returns false, the window won't be closed.
pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
let mut this = self.to_async(); let mut this = self.to_async();
self.window self.window
@ -2017,19 +2061,24 @@ impl<'a> BorrowMut<AppContext> for WindowContext<'a> {
} }
} }
/// This trait contains functionality that is shared across [ViewContext] and [WindowContext]
pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
#[doc(hidden)]
fn app_mut(&mut self) -> &mut AppContext { fn app_mut(&mut self) -> &mut AppContext {
self.borrow_mut() self.borrow_mut()
} }
#[doc(hidden)]
fn app(&self) -> &AppContext { fn app(&self) -> &AppContext {
self.borrow() self.borrow()
} }
#[doc(hidden)]
fn window(&self) -> &Window { fn window(&self) -> &Window {
self.borrow() self.borrow()
} }
#[doc(hidden)]
fn window_mut(&mut self) -> &mut Window { fn window_mut(&mut self) -> &mut Window {
self.borrow_mut() self.borrow_mut()
} }
@ -2279,6 +2328,10 @@ impl BorrowMut<Window> for WindowContext<'_> {
impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {} impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {}
/// Provides access to application state that is specialized for a particular [View].
/// Allows you to interact with focus, emit events, etc.
/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well.
/// When you call [View::<V>::update], you're passed a `&mut V` and an `&mut ViewContext<V>`.
pub struct ViewContext<'a, V> { pub struct ViewContext<'a, V> {
window_cx: WindowContext<'a>, window_cx: WindowContext<'a>,
view: &'a View<V>, view: &'a View<V>,
@ -2316,14 +2369,17 @@ impl<'a, V: 'static> ViewContext<'a, V> {
} }
} }
/// Get the entity_id of this view.
pub fn entity_id(&self) -> EntityId { pub fn entity_id(&self) -> EntityId {
self.view.entity_id() self.view.entity_id()
} }
/// Get the view pointer underlying this context.
pub fn view(&self) -> &View<V> { pub fn view(&self) -> &View<V> {
self.view self.view
} }
/// Get the model underlying this view.
pub fn model(&self) -> &Model<V> { pub fn model(&self) -> &Model<V> {
&self.view.model &self.view.model
} }
@ -2333,6 +2389,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
&mut self.window_cx &mut self.window_cx
} }
/// Set a given callback to be run on the next frame.
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static) pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)
where where
V: 'static, V: 'static,
@ -2350,6 +2407,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}); });
} }
/// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify]
pub fn observe<V2, E>( pub fn observe<V2, E>(
&mut self, &mut self,
entity: &E, entity: &E,
@ -2383,6 +2441,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
subscription subscription
} }
/// Subscribe to events emitted by another model or view.
/// The entity to which you're subscribing must implement the [EventEmitter] trait.
/// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view.
pub fn subscribe<V2, E, Evt>( pub fn subscribe<V2, E, Evt>(
&mut self, &mut self,
entity: &E, entity: &E,
@ -2440,6 +2501,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
subscription subscription
} }
/// Register a callback to be invoked when the given Model or View is released.
pub fn observe_release<V2, E>( pub fn observe_release<V2, E>(
&mut self, &mut self,
entity: &E, entity: &E,
@ -2466,6 +2528,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
subscription subscription
} }
/// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty.
/// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn.
pub fn notify(&mut self) { pub fn notify(&mut self) {
if !self.window.drawing { if !self.window.drawing {
self.window_cx.notify(); self.window_cx.notify();
@ -2475,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
} }
} }
/// Register a callback to be invoked when the window is resized.
pub fn observe_window_bounds( pub fn observe_window_bounds(
&mut self, &mut self,
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static, mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@ -2488,6 +2553,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
subscription subscription
} }
/// Register a callback to be invoked when the window is activated or deactivated.
pub fn observe_window_activation( pub fn observe_window_activation(
&mut self, &mut self,
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static, mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
@ -2620,6 +2686,10 @@ impl<'a, V: 'static> ViewContext<'a, V> {
subscription subscription
} }
/// Schedule a future to be run asynchronously.
/// The given callback is invoked with a [WeakView<V>] to avoid leaking the view for a long-running process.
/// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points.
/// The returned future will be polled on the main thread.
pub fn spawn<Fut, R>( pub fn spawn<Fut, R>(
&mut self, &mut self,
f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut, f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@ -2632,6 +2702,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
self.window_cx.spawn(|cx| f(view, cx)) self.window_cx.spawn(|cx| f(view, cx))
} }
/// Update the global state of the given type.
pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
where where
G: 'static, G: 'static,
@ -2642,6 +2713,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
result result
} }
/// Register a callback to be invoked when the given global state changes.
pub fn observe_global<G: 'static>( pub fn observe_global<G: 'static>(
&mut self, &mut self,
mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static, mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static,
@ -2660,6 +2732,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
subscription subscription
} }
/// Add a listener for any mouse event that occurs in the window.
/// This is a fairly low level method.
/// Typically, you'll want to use methods on UI elements, which perform bounds checking etc.
pub fn on_mouse_event<Event: 'static>( pub fn on_mouse_event<Event: 'static>(
&mut self, &mut self,
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
@ -2672,6 +2747,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}); });
} }
/// Register a callback to be invoked when the given Key Event is dispatched to the window.
pub fn on_key_event<Event: 'static>( pub fn on_key_event<Event: 'static>(
&mut self, &mut self,
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
@ -2684,6 +2760,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}); });
} }
/// Register a callback to be invoked when the given Action type is dispatched to the window.
pub fn on_action( pub fn on_action(
&mut self, &mut self,
action_type: TypeId, action_type: TypeId,
@ -2698,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}); });
} }
/// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe].
pub fn emit<Evt>(&mut self, event: Evt) pub fn emit<Evt>(&mut self, event: Evt)
where where
Evt: 'static, Evt: 'static,
@ -2711,6 +2789,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}); });
} }
/// Move focus to the current view, assuming it implements [FocusableView].
pub fn focus_self(&mut self) pub fn focus_self(&mut self)
where where
V: FocusableView, V: FocusableView,
@ -2718,6 +2797,11 @@ impl<'a, V: 'static> ViewContext<'a, V> {
self.defer(|view, cx| view.focus_handle(cx).focus(cx)) self.defer(|view, cx| view.focus_handle(cx).focus(cx))
} }
/// Convenience method for accessing view state in an event callback.
///
/// Many GPUI callbacks take the form of `Fn(&E, &mut WindowContext)`,
/// but it's often useful to be able to access view state in these
/// callbacks. This method provides a convenient way to do so.
pub fn listener<E>( pub fn listener<E>(
&self, &self,
f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static, f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
@ -2827,14 +2911,20 @@ impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> {
} }
// #[derive(Clone, Copy, Eq, PartialEq, Hash)] // #[derive(Clone, Copy, Eq, PartialEq, Hash)]
slotmap::new_key_type! { pub struct WindowId; } slotmap::new_key_type! {
/// A unique identifier for a window.
pub struct WindowId;
}
impl WindowId { impl WindowId {
/// Converts this window ID to a `u64`.
pub fn as_u64(&self) -> u64 { pub fn as_u64(&self) -> u64 {
self.0.as_ffi() self.0.as_ffi()
} }
} }
/// A handle to a window with a specific root view type.
/// Note that this does not keep the window alive on its own.
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct WindowHandle<V> { pub struct WindowHandle<V> {
#[deref] #[deref]
@ -2844,6 +2934,8 @@ pub struct WindowHandle<V> {
} }
impl<V: 'static + Render> WindowHandle<V> { impl<V: 'static + Render> WindowHandle<V> {
/// Create a new handle from a window ID.
/// This does not check if the root type of the window is `V`.
pub fn new(id: WindowId) -> Self { pub fn new(id: WindowId) -> Self {
WindowHandle { WindowHandle {
any_handle: AnyWindowHandle { any_handle: AnyWindowHandle {
@ -2854,6 +2946,9 @@ impl<V: 'static + Render> WindowHandle<V> {
} }
} }
/// Get the root view out of this window.
///
/// This will fail if the window is closed or if the root view's type does not match `V`.
pub fn root<C>(&self, cx: &mut C) -> Result<View<V>> pub fn root<C>(&self, cx: &mut C) -> Result<View<V>>
where where
C: Context, C: Context,
@ -2865,6 +2960,9 @@ impl<V: 'static + Render> WindowHandle<V> {
})) }))
} }
/// Update the root view of this window.
///
/// This will fail if the window has been closed or if the root view's type does not match
pub fn update<C, R>( pub fn update<C, R>(
&self, &self,
cx: &mut C, cx: &mut C,
@ -2881,6 +2979,9 @@ impl<V: 'static + Render> WindowHandle<V> {
})? })?
} }
/// Read the root view out of this window.
///
/// This will fail if the window is closed or if the root view's type does not match `V`.
pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> { pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> {
let x = cx let x = cx
.windows .windows
@ -2897,6 +2998,9 @@ impl<V: 'static + Render> WindowHandle<V> {
Ok(x.read(cx)) Ok(x.read(cx))
} }
/// Read the root view out of this window, with a callback
///
/// This will fail if the window is closed or if the root view's type does not match `V`.
pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R> pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
where where
C: Context, C: Context,
@ -2904,6 +3008,9 @@ impl<V: 'static + Render> WindowHandle<V> {
cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx)) cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))
} }
/// Read the root view pointer off of this window.
///
/// This will fail if the window is closed or if the root view's type does not match `V`.
pub fn root_view<C>(&self, cx: &C) -> Result<View<V>> pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>
where where
C: Context, C: Context,
@ -2911,6 +3018,9 @@ impl<V: 'static + Render> WindowHandle<V> {
cx.read_window(self, |root_view, _cx| root_view.clone()) cx.read_window(self, |root_view, _cx| root_view.clone())
} }
/// Check if this window is 'active'.
///
/// Will return `None` if the window is closed.
pub fn is_active(&self, cx: &AppContext) -> Option<bool> { pub fn is_active(&self, cx: &AppContext) -> Option<bool> {
cx.windows cx.windows
.get(self.id) .get(self.id)
@ -2946,6 +3056,7 @@ impl<V: 'static> From<WindowHandle<V>> for AnyWindowHandle {
} }
} }
/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type.
#[derive(Copy, Clone, PartialEq, Eq, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct AnyWindowHandle { pub struct AnyWindowHandle {
pub(crate) id: WindowId, pub(crate) id: WindowId,
@ -2953,10 +3064,13 @@ pub struct AnyWindowHandle {
} }
impl AnyWindowHandle { impl AnyWindowHandle {
/// Get the ID of this window.
pub fn window_id(&self) -> WindowId { pub fn window_id(&self) -> WindowId {
self.id self.id
} }
/// Attempt to convert this handle to a window handle with a specific root view type.
/// If the types do not match, this will return `None`.
pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> { pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> {
if TypeId::of::<T>() == self.state_type { if TypeId::of::<T>() == self.state_type {
Some(WindowHandle { Some(WindowHandle {
@ -2968,6 +3082,9 @@ impl AnyWindowHandle {
} }
} }
/// Update the state of the root view of this window.
///
/// This will fail if the window has been closed.
pub fn update<C, R>( pub fn update<C, R>(
self, self,
cx: &mut C, cx: &mut C,
@ -2979,6 +3096,9 @@ impl AnyWindowHandle {
cx.update_window(self, update) cx.update_window(self, update)
} }
/// Read the state of the root view of this window.
///
/// This will fail if the window has been closed.
pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R> pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
where where
C: Context, C: Context,
@ -2999,12 +3119,21 @@ impl AnyWindowHandle {
// } // }
// } // }
/// An identifier for an [Element].
///
/// Can be constructed with a string, a number, or both, as well
/// as other internal representations.
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum ElementId { pub enum ElementId {
/// The id of a View element
View(EntityId), View(EntityId),
/// An integer id
Integer(usize), Integer(usize),
/// A string based id
Name(SharedString), Name(SharedString),
/// An id that's equated with a focus handle
FocusHandle(FocusId), FocusHandle(FocusId),
/// A combination of a name and an integer
NamedInteger(SharedString, usize), NamedInteger(SharedString, usize),
} }
@ -3074,7 +3203,8 @@ impl From<(&'static str, u64)> for ElementId {
} }
} }
/// A rectangle, to be rendered on the screen by GPUI at the given position and size. /// A rectangle to be rendered in the window at the given position and size.
/// Passed as an argument [WindowContext::paint_quad].
#[derive(Clone)] #[derive(Clone)]
pub struct PaintQuad { pub struct PaintQuad {
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,

View file

@ -7,26 +7,56 @@ mod test;
use proc_macro::TokenStream; use proc_macro::TokenStream;
#[proc_macro] #[proc_macro]
/// register_action! can be used to register an action with the GPUI runtime.
/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead,
/// but this can be used for fine grained customization.
pub fn register_action(ident: TokenStream) -> TokenStream { pub fn register_action(ident: TokenStream) -> TokenStream {
register_action::register_action_macro(ident) register_action::register_action_macro(ident)
} }
#[proc_macro_derive(IntoElement)] #[proc_macro_derive(IntoElement)]
// #[derive(IntoElement)] is used to create a Component out of anything that implements
// the `RenderOnce` trait.
pub fn derive_into_element(input: TokenStream) -> TokenStream { pub fn derive_into_element(input: TokenStream) -> TokenStream {
derive_into_element::derive_into_element(input) derive_into_element::derive_into_element(input)
} }
#[proc_macro_derive(Render)] #[proc_macro_derive(Render)]
#[doc(hidden)]
pub fn derive_render(input: TokenStream) -> TokenStream { pub fn derive_render(input: TokenStream) -> TokenStream {
derive_render::derive_render(input) derive_render::derive_render(input)
} }
// Used by gpui to generate the style helpers.
#[proc_macro] #[proc_macro]
#[doc(hidden)]
pub fn style_helpers(input: TokenStream) -> TokenStream { pub fn style_helpers(input: TokenStream) -> TokenStream {
style_helpers::style_helpers(input) style_helpers::style_helpers(input)
} }
#[proc_macro_attribute] #[proc_macro_attribute]
/// #[gpui::test] can be used to annotate test functions that run with GPUI support.
/// it supports both synchronous and asynchronous tests, and can provide you with
/// as many `TestAppContext` instances as you need.
/// The output contains a `#[test]` annotation so this can be used with any existing
/// test harness (`cargo test` or `cargo-nextest`).
///
/// ```
/// #[gpui::test]
/// async fn test_foo(mut cx: &TestAppContext) { }
/// ```
///
/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance.
/// this will be seeded with the `SEED` environment variable and is used internally by
/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests.
/// Using the same `StdRng` for behaviour in your test will allow you to exercise a wide
/// variety of scenarios and interleavings just by changing the seed.
///
/// #[gpui::test] also takes three different arguments:
/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED.
/// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass.
/// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the
/// tests fail so that you can write out more detail about the failure.
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
test::test(args, function) test::test(args, function)
} }