platform.rs

  1use crate::{
  2    AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap,
  3    Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance,
  4    WindowParams,
  5};
  6use anyhow::Result;
  7use collections::VecDeque;
  8use futures::channel::oneshot;
  9use parking_lot::Mutex;
 10use std::{
 11    cell::RefCell,
 12    path::{Path, PathBuf},
 13    rc::{Rc, Weak},
 14    sync::Arc,
 15};
 16#[cfg(target_os = "windows")]
 17use windows::Win32::{
 18    Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
 19    System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
 20};
 21
 22/// TestPlatform implements the Platform trait for use in tests.
 23pub(crate) struct TestPlatform {
 24    background_executor: BackgroundExecutor,
 25    foreground_executor: ForegroundExecutor,
 26
 27    pub(crate) active_window: RefCell<Option<TestWindow>>,
 28    active_display: Rc<dyn PlatformDisplay>,
 29    active_cursor: Mutex<CursorStyle>,
 30    current_clipboard_item: Mutex<Option<ClipboardItem>>,
 31    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 32    current_primary_item: Mutex<Option<ClipboardItem>>,
 33    pub(crate) prompts: RefCell<TestPrompts>,
 34    pub opened_url: RefCell<Option<String>>,
 35    pub text_system: Arc<dyn PlatformTextSystem>,
 36    #[cfg(target_os = "windows")]
 37    bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
 38    weak: Weak<Self>,
 39}
 40
 41#[derive(Default)]
 42pub(crate) struct TestPrompts {
 43    multiple_choice: VecDeque<oneshot::Sender<usize>>,
 44    new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
 45}
 46
 47impl TestPlatform {
 48    pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
 49        #[cfg(target_os = "windows")]
 50        let bitmap_factory = unsafe {
 51            windows::Win32::System::Ole::OleInitialize(None)
 52                .expect("unable to initialize Windows OLE");
 53            std::mem::ManuallyDrop::new(
 54                CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
 55                    .expect("Error creating bitmap factory."),
 56            )
 57        };
 58
 59        #[cfg(target_os = "macos")]
 60        let text_system = Arc::new(crate::platform::mac::MacTextSystem::new());
 61
 62        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 63        let text_system = Arc::new(crate::platform::linux::CosmicTextSystem::new());
 64
 65        #[cfg(target_os = "windows")]
 66        let text_system = Arc::new(
 67            crate::platform::windows::DirectWriteTextSystem::new(&bitmap_factory)
 68                .expect("Unable to initialize direct write."),
 69        );
 70
 71        Rc::new_cyclic(|weak| TestPlatform {
 72            background_executor: executor,
 73            foreground_executor,
 74            prompts: Default::default(),
 75            active_cursor: Default::default(),
 76            active_display: Rc::new(TestDisplay::new()),
 77            active_window: Default::default(),
 78            current_clipboard_item: Mutex::new(None),
 79            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 80            current_primary_item: Mutex::new(None),
 81            weak: weak.clone(),
 82            opened_url: Default::default(),
 83            #[cfg(target_os = "windows")]
 84            bitmap_factory,
 85            text_system,
 86        })
 87    }
 88
 89    pub(crate) fn simulate_new_path_selection(
 90        &self,
 91        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
 92    ) {
 93        let (path, tx) = self
 94            .prompts
 95            .borrow_mut()
 96            .new_path
 97            .pop_front()
 98            .expect("no pending new path prompt");
 99        tx.send(Ok(select_path(&path))).ok();
100    }
101
102    pub(crate) fn simulate_prompt_answer(&self, response_ix: usize) {
103        let tx = self
104            .prompts
105            .borrow_mut()
106            .multiple_choice
107            .pop_front()
108            .expect("no pending multiple choice prompt");
109        self.background_executor().set_waiting_hint(None);
110        tx.send(response_ix).ok();
111    }
112
113    pub(crate) fn has_pending_prompt(&self) -> bool {
114        !self.prompts.borrow().multiple_choice.is_empty()
115    }
116
117    pub(crate) fn prompt(&self, msg: &str, detail: Option<&str>) -> oneshot::Receiver<usize> {
118        let (tx, rx) = oneshot::channel();
119        self.background_executor()
120            .set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
121        self.prompts.borrow_mut().multiple_choice.push_back(tx);
122        rx
123    }
124
125    pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
126        let executor = self.foreground_executor().clone();
127        let previous_window = self.active_window.borrow_mut().take();
128        self.active_window.borrow_mut().clone_from(&window);
129
130        executor
131            .spawn(async move {
132                if let Some(previous_window) = previous_window {
133                    if let Some(window) = window.as_ref() {
134                        if Rc::ptr_eq(&previous_window.0, &window.0) {
135                            return;
136                        }
137                    }
138                    previous_window.simulate_active_status_change(false);
139                }
140                if let Some(window) = window {
141                    window.simulate_active_status_change(true);
142                }
143            })
144            .detach();
145    }
146
147    pub(crate) fn did_prompt_for_new_path(&self) -> bool {
148        !self.prompts.borrow().new_path.is_empty()
149    }
150}
151
152impl Platform for TestPlatform {
153    fn background_executor(&self) -> BackgroundExecutor {
154        self.background_executor.clone()
155    }
156
157    fn foreground_executor(&self) -> ForegroundExecutor {
158        self.foreground_executor.clone()
159    }
160
161    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
162        self.text_system.clone()
163    }
164
165    fn keyboard_layout(&self) -> String {
166        "zed.keyboard.example".to_string()
167    }
168
169    fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
170
171    fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
172        unimplemented!()
173    }
174
175    fn quit(&self) {}
176
177    fn restart(&self, _: Option<PathBuf>) {
178        unimplemented!()
179    }
180
181    fn activate(&self, _ignoring_other_apps: bool) {
182        //
183    }
184
185    fn hide(&self) {
186        unimplemented!()
187    }
188
189    fn hide_other_apps(&self) {
190        unimplemented!()
191    }
192
193    fn unhide_other_apps(&self) {
194        unimplemented!()
195    }
196
197    fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
198        vec![self.active_display.clone()]
199    }
200
201    fn primary_display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
202        Some(self.active_display.clone())
203    }
204
205    fn active_window(&self) -> Option<crate::AnyWindowHandle> {
206        self.active_window
207            .borrow()
208            .as_ref()
209            .map(|window| window.0.lock().handle)
210    }
211
212    fn open_window(
213        &self,
214        handle: AnyWindowHandle,
215        params: WindowParams,
216    ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
217        let window = TestWindow::new(
218            handle,
219            params,
220            self.weak.clone(),
221            self.active_display.clone(),
222        );
223        Ok(Box::new(window))
224    }
225
226    fn window_appearance(&self) -> WindowAppearance {
227        WindowAppearance::Light
228    }
229
230    fn open_url(&self, url: &str) {
231        *self.opened_url.borrow_mut() = Some(url.to_string())
232    }
233
234    fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
235        unimplemented!()
236    }
237
238    fn prompt_for_paths(
239        &self,
240        _options: crate::PathPromptOptions,
241    ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
242        unimplemented!()
243    }
244
245    fn prompt_for_new_path(
246        &self,
247        directory: &std::path::Path,
248    ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
249        let (tx, rx) = oneshot::channel();
250        self.prompts
251            .borrow_mut()
252            .new_path
253            .push_back((directory.to_path_buf(), tx));
254        rx
255    }
256
257    fn reveal_path(&self, _path: &std::path::Path) {
258        unimplemented!()
259    }
260
261    fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
262
263    fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
264        unimplemented!()
265    }
266
267    fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
268    fn set_dock_menu(&self, _menu: Vec<crate::MenuItem>, _keymap: &Keymap) {}
269
270    fn add_recent_document(&self, _paths: &Path) {}
271
272    fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
273
274    fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
275
276    fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
277
278    fn app_path(&self) -> Result<std::path::PathBuf> {
279        unimplemented!()
280    }
281
282    fn path_for_auxiliary_executable(&self, _name: &str) -> Result<std::path::PathBuf> {
283        unimplemented!()
284    }
285
286    fn set_cursor_style(&self, style: crate::CursorStyle) {
287        *self.active_cursor.lock() = style;
288    }
289
290    fn should_auto_hide_scrollbars(&self) -> bool {
291        false
292    }
293
294    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
295    fn write_to_primary(&self, item: ClipboardItem) {
296        *self.current_primary_item.lock() = Some(item);
297    }
298
299    fn write_to_clipboard(&self, item: ClipboardItem) {
300        *self.current_clipboard_item.lock() = Some(item);
301    }
302
303    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
304    fn read_from_primary(&self) -> Option<ClipboardItem> {
305        self.current_primary_item.lock().clone()
306    }
307
308    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
309        self.current_clipboard_item.lock().clone()
310    }
311
312    fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
313        Task::ready(Ok(()))
314    }
315
316    fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
317        Task::ready(Ok(None))
318    }
319
320    fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
321        Task::ready(Ok(()))
322    }
323
324    fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
325        unimplemented!()
326    }
327
328    fn open_with_system(&self, _path: &Path) {
329        unimplemented!()
330    }
331}
332
333#[cfg(target_os = "windows")]
334impl Drop for TestPlatform {
335    fn drop(&mut self) {
336        unsafe {
337            std::mem::ManuallyDrop::drop(&mut self.bitmap_factory);
338            windows::Win32::System::Ole::OleUninitialize();
339        }
340    }
341}