platform.rs

  1use crate::{
  2    AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
  3    ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
  4    PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
  5    Size, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
  6};
  7use anyhow::Result;
  8use collections::VecDeque;
  9use futures::channel::oneshot;
 10use parking_lot::Mutex;
 11use std::{
 12    cell::RefCell,
 13    path::{Path, PathBuf},
 14    rc::{Rc, Weak},
 15    sync::Arc,
 16};
 17#[cfg(target_os = "windows")]
 18use windows::Win32::{
 19    Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
 20    System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance},
 21};
 22
 23/// TestPlatform implements the Platform trait for use in tests.
 24pub(crate) struct TestPlatform {
 25    background_executor: BackgroundExecutor,
 26    foreground_executor: ForegroundExecutor,
 27
 28    pub(crate) active_window: RefCell<Option<TestWindow>>,
 29    active_display: Rc<dyn PlatformDisplay>,
 30    active_cursor: Mutex<CursorStyle>,
 31    current_clipboard_item: Mutex<Option<ClipboardItem>>,
 32    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 33    current_primary_item: Mutex<Option<ClipboardItem>>,
 34    pub(crate) prompts: RefCell<TestPrompts>,
 35    screen_capture_sources: RefCell<Vec<TestScreenCaptureSource>>,
 36    pub opened_url: RefCell<Option<String>>,
 37    pub text_system: Arc<dyn PlatformTextSystem>,
 38    #[cfg(target_os = "windows")]
 39    bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
 40    weak: Weak<Self>,
 41}
 42
 43#[derive(Clone)]
 44/// A fake screen capture source, used for testing.
 45pub struct TestScreenCaptureSource {}
 46
 47pub struct TestScreenCaptureStream {}
 48
 49impl ScreenCaptureSource for TestScreenCaptureSource {
 50    fn resolution(&self) -> Result<Size<DevicePixels>> {
 51        Ok(size(DevicePixels(1), DevicePixels(1)))
 52    }
 53
 54    fn stream(
 55        &self,
 56        _foreground_executor: &ForegroundExecutor,
 57        _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
 58    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
 59        let (mut tx, rx) = oneshot::channel();
 60        let stream = TestScreenCaptureStream {};
 61        tx.send(Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>))
 62            .ok();
 63        rx
 64    }
 65}
 66
 67impl ScreenCaptureStream for TestScreenCaptureStream {}
 68
 69struct TestPrompt {
 70    msg: String,
 71    detail: Option<String>,
 72    answers: Vec<String>,
 73    tx: oneshot::Sender<usize>,
 74}
 75
 76#[derive(Default)]
 77pub(crate) struct TestPrompts {
 78    multiple_choice: VecDeque<TestPrompt>,
 79    new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
 80}
 81
 82impl TestPlatform {
 83    pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
 84        #[cfg(target_os = "windows")]
 85        let bitmap_factory = unsafe {
 86            windows::Win32::System::Ole::OleInitialize(None)
 87                .expect("unable to initialize Windows OLE");
 88            std::mem::ManuallyDrop::new(
 89                CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
 90                    .expect("Error creating bitmap factory."),
 91            )
 92        };
 93
 94        let text_system = Arc::new(NoopTextSystem);
 95
 96        Rc::new_cyclic(|weak| TestPlatform {
 97            background_executor: executor,
 98            foreground_executor,
 99            prompts: Default::default(),
100            screen_capture_sources: Default::default(),
101            active_cursor: Default::default(),
102            active_display: Rc::new(TestDisplay::new()),
103            active_window: Default::default(),
104            current_clipboard_item: Mutex::new(None),
105            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
106            current_primary_item: Mutex::new(None),
107            weak: weak.clone(),
108            opened_url: Default::default(),
109            #[cfg(target_os = "windows")]
110            bitmap_factory,
111            text_system,
112        })
113    }
114
115    pub(crate) fn simulate_new_path_selection(
116        &self,
117        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
118    ) {
119        let (path, tx) = self
120            .prompts
121            .borrow_mut()
122            .new_path
123            .pop_front()
124            .expect("no pending new path prompt");
125        self.background_executor().set_waiting_hint(None);
126        tx.send(Ok(select_path(&path))).ok();
127    }
128
129    #[track_caller]
130    pub(crate) fn simulate_prompt_answer(&self, response: &str) {
131        let prompt = self
132            .prompts
133            .borrow_mut()
134            .multiple_choice
135            .pop_front()
136            .expect("no pending multiple choice prompt");
137        self.background_executor().set_waiting_hint(None);
138        let Some(ix) = prompt.answers.iter().position(|a| a == response) else {
139            panic!(
140                "PROMPT: {}\n{:?}\n{:?}\nCannot respond with {}",
141                prompt.msg, prompt.detail, prompt.answers, response
142            )
143        };
144        prompt.tx.send(ix).ok();
145    }
146
147    pub(crate) fn has_pending_prompt(&self) -> bool {
148        !self.prompts.borrow().multiple_choice.is_empty()
149    }
150
151    pub(crate) fn pending_prompt(&self) -> Option<(String, String)> {
152        let prompts = self.prompts.borrow();
153        let prompt = prompts.multiple_choice.front()?;
154        Some((
155            prompt.msg.clone(),
156            prompt.detail.clone().unwrap_or_default(),
157        ))
158    }
159
160    pub(crate) fn set_screen_capture_sources(&self, sources: Vec<TestScreenCaptureSource>) {
161        *self.screen_capture_sources.borrow_mut() = sources;
162    }
163
164    pub(crate) fn prompt(
165        &self,
166        msg: &str,
167        detail: Option<&str>,
168        answers: &[PromptButton],
169    ) -> oneshot::Receiver<usize> {
170        let (tx, rx) = oneshot::channel();
171        let answers: Vec<String> = answers.iter().map(|s| s.label().to_string()).collect();
172        self.background_executor()
173            .set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
174        self.prompts
175            .borrow_mut()
176            .multiple_choice
177            .push_back(TestPrompt {
178                msg: msg.to_string(),
179                detail: detail.map(|s| s.to_string()),
180                answers: answers.clone(),
181                tx,
182            });
183        rx
184    }
185
186    pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
187        let executor = self.foreground_executor().clone();
188        let previous_window = self.active_window.borrow_mut().take();
189        self.active_window.borrow_mut().clone_from(&window);
190
191        executor
192            .spawn(async move {
193                if let Some(previous_window) = previous_window {
194                    if let Some(window) = window.as_ref() {
195                        if Rc::ptr_eq(&previous_window.0, &window.0) {
196                            return;
197                        }
198                    }
199                    previous_window.simulate_active_status_change(false);
200                }
201                if let Some(window) = window {
202                    window.simulate_active_status_change(true);
203                }
204            })
205            .detach();
206    }
207
208    pub(crate) fn did_prompt_for_new_path(&self) -> bool {
209        !self.prompts.borrow().new_path.is_empty()
210    }
211}
212
213impl Platform for TestPlatform {
214    fn background_executor(&self) -> BackgroundExecutor {
215        self.background_executor.clone()
216    }
217
218    fn foreground_executor(&self) -> ForegroundExecutor {
219        self.foreground_executor.clone()
220    }
221
222    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
223        self.text_system.clone()
224    }
225
226    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
227        Box::new(TestKeyboardLayout)
228    }
229
230    fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
231
232    fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
233        unimplemented!()
234    }
235
236    fn quit(&self) {}
237
238    fn restart(&self, _: Option<PathBuf>) {
239        //
240    }
241
242    fn activate(&self, _ignoring_other_apps: bool) {
243        //
244    }
245
246    fn hide(&self) {
247        unimplemented!()
248    }
249
250    fn hide_other_apps(&self) {
251        unimplemented!()
252    }
253
254    fn unhide_other_apps(&self) {
255        unimplemented!()
256    }
257
258    fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
259        vec![self.active_display.clone()]
260    }
261
262    fn primary_display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
263        Some(self.active_display.clone())
264    }
265
266    fn is_screen_capture_supported(&self) -> bool {
267        true
268    }
269
270    fn screen_capture_sources(
271        &self,
272    ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
273        let (mut tx, rx) = oneshot::channel();
274        tx.send(Ok(self
275            .screen_capture_sources
276            .borrow()
277            .iter()
278            .map(|source| Box::new(source.clone()) as Box<dyn ScreenCaptureSource>)
279            .collect()))
280            .ok();
281        rx
282    }
283
284    fn active_window(&self) -> Option<crate::AnyWindowHandle> {
285        self.active_window
286            .borrow()
287            .as_ref()
288            .map(|window| window.0.lock().handle)
289    }
290
291    fn open_window(
292        &self,
293        handle: AnyWindowHandle,
294        params: WindowParams,
295    ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
296        let window = TestWindow::new(
297            handle,
298            params,
299            self.weak.clone(),
300            self.active_display.clone(),
301        );
302        Ok(Box::new(window))
303    }
304
305    fn window_appearance(&self) -> WindowAppearance {
306        WindowAppearance::Light
307    }
308
309    fn open_url(&self, url: &str) {
310        *self.opened_url.borrow_mut() = Some(url.to_string())
311    }
312
313    fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
314        unimplemented!()
315    }
316
317    fn prompt_for_paths(
318        &self,
319        _options: crate::PathPromptOptions,
320    ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
321        unimplemented!()
322    }
323
324    fn prompt_for_new_path(
325        &self,
326        directory: &std::path::Path,
327    ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
328        let (tx, rx) = oneshot::channel();
329        self.background_executor()
330            .set_waiting_hint(Some(format!("PROMPT FOR PATH: {:?}", directory)));
331        self.prompts
332            .borrow_mut()
333            .new_path
334            .push_back((directory.to_path_buf(), tx));
335        rx
336    }
337
338    fn can_select_mixed_files_and_dirs(&self) -> bool {
339        true
340    }
341
342    fn reveal_path(&self, _path: &std::path::Path) {
343        unimplemented!()
344    }
345
346    fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
347
348    fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
349        unimplemented!()
350    }
351
352    fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
353    fn set_dock_menu(&self, _menu: Vec<crate::MenuItem>, _keymap: &Keymap) {}
354
355    fn add_recent_document(&self, _paths: &Path) {}
356
357    fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
358
359    fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
360
361    fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
362
363    fn app_path(&self) -> Result<std::path::PathBuf> {
364        unimplemented!()
365    }
366
367    fn path_for_auxiliary_executable(&self, _name: &str) -> Result<std::path::PathBuf> {
368        unimplemented!()
369    }
370
371    fn set_cursor_style(&self, style: crate::CursorStyle) {
372        *self.active_cursor.lock() = style;
373    }
374
375    fn should_auto_hide_scrollbars(&self) -> bool {
376        false
377    }
378
379    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
380    fn write_to_primary(&self, item: ClipboardItem) {
381        *self.current_primary_item.lock() = Some(item);
382    }
383
384    fn write_to_clipboard(&self, item: ClipboardItem) {
385        *self.current_clipboard_item.lock() = Some(item);
386    }
387
388    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
389    fn read_from_primary(&self) -> Option<ClipboardItem> {
390        self.current_primary_item.lock().clone()
391    }
392
393    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
394        self.current_clipboard_item.lock().clone()
395    }
396
397    fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
398        Task::ready(Ok(()))
399    }
400
401    fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
402        Task::ready(Ok(None))
403    }
404
405    fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
406        Task::ready(Ok(()))
407    }
408
409    fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
410        unimplemented!()
411    }
412
413    fn open_with_system(&self, _path: &Path) {
414        unimplemented!()
415    }
416}
417
418impl TestScreenCaptureSource {
419    /// Create a fake screen capture source, for testing.
420    pub fn new() -> Self {
421        Self {}
422    }
423}
424
425#[cfg(target_os = "windows")]
426impl Drop for TestPlatform {
427    fn drop(&mut self) {
428        unsafe {
429            std::mem::ManuallyDrop::drop(&mut self.bitmap_factory);
430            windows::Win32::System::Ole::OleUninitialize();
431        }
432    }
433}
434
435struct TestKeyboardLayout;
436
437impl PlatformKeyboardLayout for TestKeyboardLayout {
438    fn id(&self) -> &str {
439        "zed.keyboard.example"
440    }
441
442    fn name(&self) -> &str {
443        "zed.keyboard.example"
444    }
445}