Skip to main content

gpui_platform/
gpui_platform.rs

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