Skip to main content

gpui/
platform_entry.rs

1//! Platform entrypoints: construct the default [`crate::Platform`] for the
2//! current operating system and turn it into an [`crate::Application`].
3//!
4//! These live behind the `platform` feature, which pulls in the
5//! backend for the target OS.
6
7use crate::{Application, BackgroundExecutor, Platform};
8use std::rc::Rc;
9
10/// Returns a background executor for the current platform.
11pub fn background_executor() -> BackgroundExecutor {
12    current_platform(true).background_executor()
13}
14
15/// Returns an [`Application`] backed by the default platform for this OS.
16pub fn application() -> Application {
17    #[cfg(target_family = "wasm")]
18    {
19        application_with_web_backend(gpui_web::WebBackendPreference::Auto)
20    }
21
22    #[cfg(not(target_family = "wasm"))]
23    Application::with_platform(current_platform(false))
24}
25
26/// Returns a headless [`Application`] backed by the default platform for this OS.
27pub fn headless() -> Application {
28    Application::with_platform(current_platform(true))
29}
30
31#[cfg(target_family = "wasm")]
32pub use gpui_web::WebBackendPreference;
33
34#[cfg(target_family = "wasm")]
35pub fn application_with_web_backend(backend_preference: WebBackendPreference) -> Application {
36    let platform = Rc::new(gpui_web::WebPlatform::new_with_backend(
37        true,
38        backend_preference,
39    ));
40    let http_client = std::sync::Arc::new(platform.fetch_http_client());
41    Application::with_platform(platform).with_http_client(http_client)
42}
43
44/// Unlike `application`, this function returns a single-threaded web application.
45#[cfg(target_family = "wasm")]
46pub fn single_threaded_web() -> Application {
47    let platform = Rc::new(gpui_web::WebPlatform::new(false));
48    let http_client = std::sync::Arc::new(platform.fetch_http_client());
49    Application::with_platform(platform).with_http_client(http_client)
50}
51
52/// Initializes logging for the web platform. `gpui_web::init_logging` also
53/// installs the panic hook. Call this before running the application in a
54/// wasm_bindgen entrypoint.
55#[cfg(target_family = "wasm")]
56pub fn web_init() {
57    gpui_web::init_logging();
58}
59
60/// Returns the default [`Platform`] for the current OS.
61pub fn current_platform(headless: bool) -> Rc<dyn Platform> {
62    #[cfg(target_os = "macos")]
63    {
64        Rc::new(gpui_macos::MacPlatform::new(headless))
65    }
66
67    #[cfg(target_os = "windows")]
68    {
69        Rc::new(
70            gpui_windows::WindowsPlatform::new(headless)
71                .expect("failed to initialize Windows platform"),
72        )
73    }
74
75    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
76    {
77        gpui_linux::current_platform(headless)
78    }
79
80    #[cfg(target_family = "wasm")]
81    {
82        let _ = headless;
83        Rc::new(gpui_web::WebPlatform::new(true))
84    }
85}
86
87/// Returns a new [`crate::SceneRenderer`] for the current platform, if available.
88#[cfg(any(feature = "bench-support", feature = "test-support"))]
89pub fn current_headless_renderer() -> Option<Box<dyn crate::SceneRenderer>> {
90    #[cfg(target_os = "macos")]
91    {
92        Some(Box::new(
93            gpui_macos::metal_renderer::MetalHeadlessRenderer::new(),
94        ))
95    }
96
97    #[cfg(not(target_os = "macos"))]
98    {
99        None
100    }
101}
102
103#[cfg(all(test, target_os = "macos"))]
104mod tests {
105    use super::*;
106    use crate::{AppContext, Empty, VisualTestAppContext};
107    use std::cell::RefCell;
108    use std::time::Duration;
109
110    // Note: All VisualTestAppContext tests are ignored by default because they require
111    // the macOS main thread. Standard Rust tests run on worker threads, which causes
112    // SIGABRT when interacting with macOS AppKit/Cocoa APIs.
113    //
114    // To run these tests, use:
115    // cargo test -p gpui visual_test_context -- --ignored --test-threads=1
116
117    #[test]
118    #[ignore] // Requires macOS main thread
119    fn test_foreground_tasks_run_with_run_until_parked() {
120        let mut cx = VisualTestAppContext::new(current_platform(false));
121
122        let task_ran = Rc::new(RefCell::new(false));
123
124        // Spawn a foreground task via the App's spawn method
125        // This should use our TestDispatcher, not the MacDispatcher
126        {
127            let task_ran = task_ran.clone();
128            cx.update(|cx| {
129                cx.spawn(async move |_| {
130                    *task_ran.borrow_mut() = true;
131                })
132                .detach();
133            });
134        }
135
136        // The task should not have run yet
137        assert!(!*task_ran.borrow());
138
139        // Run until parked should execute the foreground task
140        cx.run_until_parked();
141
142        // Now the task should have run
143        assert!(*task_ran.borrow());
144    }
145
146    #[test]
147    #[ignore] // Requires macOS main thread
148    fn test_advance_clock_triggers_delayed_tasks() {
149        let mut cx = VisualTestAppContext::new(current_platform(false));
150
151        let task_ran = Rc::new(RefCell::new(false));
152
153        // Spawn a task that waits for a timer
154        {
155            let task_ran = task_ran.clone();
156            let executor = cx.background_executor.clone();
157            cx.update(|cx| {
158                cx.spawn(async move |_| {
159                    executor.timer(Duration::from_millis(500)).await;
160                    *task_ran.borrow_mut() = true;
161                })
162                .detach();
163            });
164        }
165
166        // Run until parked - the task should be waiting on the timer
167        cx.run_until_parked();
168        assert!(!*task_ran.borrow());
169
170        // Advance clock past the timer duration
171        cx.advance_clock(Duration::from_millis(600));
172
173        // Now the task should have completed
174        assert!(*task_ran.borrow());
175    }
176
177    #[test]
178    #[ignore] // Requires macOS main thread - window creation fails on test threads
179    fn test_window_spawn_uses_test_dispatcher() {
180        let mut cx = VisualTestAppContext::new(current_platform(false));
181
182        let task_ran = Rc::new(RefCell::new(false));
183
184        let window = cx
185            .open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
186            .expect("Failed to open window");
187
188        // Spawn a task via window.spawn - this is the critical test case
189        // for tooltip behavior, as tooltips use window.spawn for delayed show
190        {
191            let task_ran = task_ran.clone();
192            cx.update_window(window.into(), |_, window, cx| {
193                window
194                    .spawn(cx, async move |_| {
195                        *task_ran.borrow_mut() = true;
196                    })
197                    .detach();
198            })
199            .ok();
200        }
201
202        // The task should not have run yet
203        assert!(!*task_ran.borrow());
204
205        // Run until parked should execute the foreground task spawned via window
206        cx.run_until_parked();
207
208        // Now the task should have run
209        assert!(*task_ran.borrow());
210    }
211}