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